#python #tkinter #pyserial #motordriver
Вопрос:
У меня есть вопрос относительно использования кнопки «Стоп» в Tkinter
.
Для эксперимента я должен настроить X/Y-ступень, которая работает с использованием двух шаговых двигателей. Программа arduino работает идеально. Единственная проблема заключается в том, что, когда я активирую функцию запуска, которая приводит сцену в различные координаты, она зависает. Теперь проблема в том, что он должен работать неделями подряд, и ему нужна кнопка остановки для экстренных ситуаций и остановки шагового двигателя в целом. Кнопка «Стоп» должна делать две вещи: она должна останавливать двигатели шагового привода и прерывать tkinter.after
цикл. Однако из-за замерзания нажать на кнопку невозможно.
Вот мой код:
import tkinter as tk
import serial
ser = serial.Serial('COM5', 115200)
running = False
def quit():
"""Function that closes the serial port and destroys the root of the GUI"""
global root
ser.close()
root.destroy()
def route():
"""Writes coordinates to the arduino, which in return drives the stepper motors"""
if running == True:
# The g line stands for go to!
ser.write(b'g115000rn')
root.after(50)
ser.write(b'g225000rn')
root.after(30000)
ser.write(b'g1400rn')
root.after(50)
ser.write(b'g2500rn')
root.after(12000,route())
def zeroing():
"""Zeros the program, this is necessary for the stage to
calibrate it's boundary conditions"""
#zeros the stage so that it is ready to use!
varLabel.set("zeroing, please move away from the stage")
#the z command zeros the motors for boundary business
ser.write(b'zrn')
def run_program():
"""Runs the function Route and sets running to True (not a good start/stop system)"""
#starts the program, but only after you zero the stage
global running
running = True
varLabel.set("Program running")
route()
def stop_program():
"""Sets the running flag to False and sends a stop command to the arduino"""
#stops the program immediately
global running
running = False
varLabel.set("Program stopped,please zero before continuing")
#the s byte is a command that stops the stepper motors
ser.write(b'srn')
if __name__== "__main__":
root = tk.Tk()
canvas1 = tk.Canvas(root, width=800, height=400)
canvas1.pack()
root.title('XY-stage controller')
#instructions
instructions = tk.Label(root,text='Enter the amount of hours you want your measurements to last in the text box.'
'n Click on run program to start a measurement session.'
'n Click on stop incase of an emergency or if it is wanted to stop the program.',
font = "Raleway")
instructions.pack(side='bottom')
# initialize active labels
varLabel = tk.IntVar()
tkLabel = tk.Label(textvariable=varLabel,)
tkLabel.pack(side='top')
# Buttons for initializing a bunch of good functions
zerobutton = tk.IntVar()
tkrunprogram= tk.Button(
root,
text='Zero',
command = zeroing,
height = 4,
fg = "black",
width = 10,
bg = 'gray',
bd = 5,
activebackground = 'green'
)
tkrunprogram.pack(side='top')
runprogbutton = tk.IntVar()
tkrunprogram= tk.Button(
root,
text='Run Program',
command = run_program,
height = 4,
fg = "black",
width = 10,
bg = 'gray',
bd = 5,
activebackground = 'green'
)
tkrunprogram.pack(side='top')
stopbutton = tk.IntVar()
tkstopprog= tk.Button(
root,
text='Stop Program',
command = stop_program,
height = 4,
fg = "black",
width = 10,
bg = 'gray',
bd = 5,
activebackground = 'red'
)
tkstopprog.pack(side='top')
Buttonquit = tk.IntVar()
tkButtonQuit = tk.Button(
root,
text='Quit',
command = quit,
height = 4,
fg = "black",
width = 10,
bg = 'yellow',
bd = 5
)
# initialize an entry box
entry1 = tk.Entry(root)
durbox = canvas1.create_window(400, 200, window=entry1)
tkButtonQuit.pack(side='top')
root.mainloop()
Команды «После» в конце концов введут 60-минутные паузы, из — за которых программа остановится на 60 минут. Надеюсь, есть простое решение для прерывания функции!
Заранее благодарю вас!
Ответ №1:
Вы можете использовать многопоточность. Сделайте все коммуникации в отдельном потоке, а также убедитесь, что вы не обновляете компоненты графического интерфейса в дочернем потоке.
Вот минимальный пример:
import serial
import tkinter as tk
from threading import Thread
import time
def start():
global running
stop()
btn.config(text="Stop", command=stop)
running = True
info_label["text"] = "Starting..."
thread = Thread(target=run, daemon=True)
thread.start()
def run():
ser = serial.Serial("COM5", 115200, timeout=2)
while running:
ser.write(b'g115000rn')
time.sleep(50)
ser.write(b'g225000rn')
time.sleep(30000)
ser.write(b'g1400rn')
time.sleep(50)
ser.write(b'g2500rn')
ser.write(b'srn')
ser.close()
def stop():
global running
running = False
info_label["text"] = "Stopped"
btn.config(text="Start", command=start)
root = tk.Tk()
running = False
info_label = tk.Label(root, text="INFO:")
info_label.pack()
btn = tk.Button(root, text="Start", command=start)
btn.pack()
root.mainloop()
Комментарии:
1. Спасибо! Если подход Брайана не сработает, то я обязательно попробую потоковую обработку!
2. Это на самом деле сработало идеально, с некоторыми изменениями, конечно! Большое спасибо!
Ответ №2:
after(x000)
по сути, это то же самое, что time.sleep(x)
— это переводит все приложение в спящий режим. Как правило, вы никогда не должны делать это в том же потоке, что и графический интерфейс. Однако это не означает, что вам нужно использовать потоки.
after
метод tkinter позволяет планировать выполнение команд в будущем. Если вы выполняете быстрые команды, такие как отправка нескольких байтов по последовательному соединению, это действительно все, что вам нужно. Это менее сложно и требует меньших затрат, чем использование потоков.
Например, вашу route
функцию, вероятно, можно записать примерно так:
def route():
if running == True:
# immediately write this:
ser.write(b'g115000rn')
# after 50ms, write this:
root.after(50, ser.write, b'g225000')
# after 30 more seconds, write this
root.after(50 30000, ser.write, b'g1400rn')
# and then after 50ms more, write this
root.after(50 30000 50, ser.write, b'g2500rn')
# and finally, after 12 seconds, do it all again
root.after(50 30000 50 12000,route)
Как только вы позвоните один раз, вам не нужно будет звонить снова, и вам не нужно будет звонить в потоке. Это просто помещает некоторую работу в очередь, которую заберут когда-нибудь в будущем.
Поскольку каждый вызов root.after
возвращает идентификатор, вы можете сохранить эти идентификаторы, чтобы в случае необходимости остановить все, вы могли позвонить after_cancel
по каждому сохраненному идентификатору.
Другой способ-определить задание как последовательность задержек, а затем байты для записи. Например:
job = (
(0, b'g115000rn'),
(50, b'g225000'),
(30000, b'g1400rn'),
(50, b'g2500rn'),
)
Тогда ваша route
функция может выглядеть примерно так (непроверено, но это довольно близко)
def route(job):
global after_id
delay = 0
for (delta, data) in job:
delay = delta
root.after(delay, ser.write, data)
delay = 12000
root.after(delay, route, job)
Существует множество вариаций этой темы. Например, вы можете создать Job
класс, реализующий эту логику, или job
он может содержать команды, а не данные. Дело в том, что вы можете определить структуру данных, которая определяет выполняемую работу, а затем использовать after
ее для планирования этой работы.
Комментарии:
1. Большое спасибо! Я попробую 🙂