I am currently trying to program a robot arm which is controlled by a Raspberry Pi.
Everything works fine so far, except for one thing and I already googled and tried everything for many hours but can't find a working solution.
For the movement of the robot arm it is necessary to run all motors "simultaneously" with threads (works fine).
The problem I have is that I need to update a label which shows the current angle of an axis (motor) as soon as it finished its movement but other motors are still running (threads).
After a lot of research I thought I found the solution by using a queue and Tkinters after-method. But it still doesn't work as the labels text only gets updated after all threads terminated.
I wrote an example code where I want to get a label update for motor "one" which will finish its for-loop (100 iterations) before motor "two" (500 iterations). I expected the label to get updated as soon as motor one reached its target while motor two is still runing.
But although I used the after-method it still waits till motor two finished before updating the label.
Hope you can help me!
from tkinter import *
import threading
import time
from queue import *
class StepperMotors:
def __init__(self, root):
self.root = root
self.start_btn = Button(root, text="Start", command=lambda:self.start_movement())
self.start_btn.config(width = 10)
self.start_btn.grid(row=1,column=1)
self.label_one = Label(root, text='')
self.label_one.config(width = 10)
self.label_one.grid(row=2, column=1)
self.label_two = Label(root, text='')
self.label_two.config(width = 10)
self.label_two.grid(row=3, column=1)
def start_movement(self):
self.thread_queue = Queue()
self.root.after(100, self.wait_for_finish)
thread_one = threading.Thread(target=self.motor_actuation, args=(1,100))
thread_two = threading.Thread(target=self.motor_actuation, args=(2,500))
thread_one.start()
thread_two.start()
thread_one.join()
thread_two.join()
def motor_actuation(self, motor, iterations):
for i in range(iterations):
i = i+1
update_text = str(motor) + " " + str(i) + "\n"
print(update_text)
time.sleep(0.01)
self.thread_queue.put(update_text)
def wait_for_finish(self):
try:
self.text = self.thread_queue.get()
self.label_one.config(text=self.text)
except self.thread_queue.empty():
self.root.after(100, self.wait_for_finish)
if __name__ == "__main__":
root = Tk()
root.title("test")
stepper = StepperMotors(root)
root.mainloop()
It's better to use daemon thread which are non-blocking.
Also, It's better to have a separation of concerns : a robot (or robot arm) can be a object which has its own life time: a daemon thread. Idem, you can define a "LabelUpdater" which read the state of a robot and update a label.
Let's define a robot:
class Robot(threading.Thread):
def __init__(self, name: str, label_queue: queue.Queue, end_pos: int):
super().__init__(name=name)
self.daemon = True
self.label_queue = label_queue
self.end_pos = end_pos
def run(self) -> None:
for angle in range(self.end_pos):
self.label_queue.put(angle)
time.sleep(0.01)
Let's define a LabelUpdater:
class LabelUpdater(threading.Thread):
def __init__(self, name: str, label_queue: queue.Queue, root_app: tkinter.Tk, variable: tkinter.Variable):
super().__init__(name=name)
self.daemon = True
self.label_queue = label_queue
self.root_app = root_app
self.variable = variable
def run(self) -> None:
# run forever
while True:
# wait a second please
time.sleep(1)
# consume all the queue and keep only the last message
last_msg = None
while True:
try:
msg = self.label_queue.get(block=False)
except queue.Empty:
break
last_msg = msg
self.label_queue.task_done()
if last_msg:
self.variable.set(last_msg)
Then, the main application should define:
tkinter.StringVar
variable which will be updated, class StepperMotors:
def __init__(self, root):
self.root = root
self.label_one_queue = queue.Queue()
self.label_two_queue = queue.Queue()
self.start_btn = tkinter.Button(root, text="Start", command=lambda: self.start_movement())
self.start_btn.config(width=10)
self.start_btn.grid(row=1, column=1)
self.text_one = tkinter.StringVar()
self.text_one.set("one")
self.label_one = tkinter.Label(root, textvariable=self.text_one)
self.label_one.config(width=10)
self.label_one.grid(row=2, column=1)
self.text_two = tkinter.StringVar()
self.text_two.set("two")
self.label_two = tkinter.Label(root, textvariable=self.text_two)
self.label_two.config(width=10)
self.label_two.grid(row=3, column=1)
self.robot_one = Robot("robot_one", self.label_one_queue, 100)
self.robot_two = Robot("robot_two", self.label_two_queue, 500)
self.updater_one = LabelUpdater("updater_one", self.label_one_queue, self.root, self.text_one)
self.updater_two = LabelUpdater("updater_two", self.label_two_queue, self.root, self.text_two)
self.updater_one.start()
self.updater_two.start()
def start_movement(self):
self.robot_one.start()
self.robot_two.start()
Of course, you need a flag or something to check that each robot is not already running.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.