如何使用 TKinter 按钮随时中断 GPIO RPi 循环
How to interrupt a GPIO RPi loop at any time with TKinter button
这是我正在努力使用的程序的第二个版本。我在程序的第一个版本中发布了一个较早的问题,但没有成功。
这是我的 "Big Picture Idea" 以防它帮助您找到我偏离轨道的地方。我有很多植物要浇水,我想用 TKinter 制作一个 GUI,它将出现在 Raspberry Pi 的触摸屏上。 Pi 控制一个连接到水泵的 110V 继电器开关。它只是基本的 "on/off" 循环,用于 z # 株植物。
用户可以使用三个滑块来调整 X、Y 和 Z 值。 X 是水泵需要打开多长时间,Y 是水泵需要关闭多长时间(这样您就有时间将软管移动到下一个工厂),Z 是您必须关闭的工厂总数水。我曾经只是让循环 运行 永远,因为我总是可以 CTRL-C 结束程序,但现在我试图在不需要键盘的情况下在触摸屏上完成它 - 比教别人更用户友好谁不知道 Linux 意味着如何使用终端。它还需要 运行 像人们习惯看到的程序一样,因此需要 GUI 触摸屏按钮(如智能手机和平板电脑)。
该代码大部分运行良好,停止和启动时不会崩溃。我找到了开始按钮,还有一个重置按钮可以清除滑块值,以防您想从零开始重新开始这些值。但似乎影响这里很多人的大问题是该死的“退出”按钮。
我需要用户能够在循环期间随时按下 TKinter EXIT 按钮,并让程序知道何时按下按钮并立即服从它,基本上是中断循环。我尝试了很多东西,没有任何效果。 Checkbuttons 只会等到循环结束,然后更新切换值。那时为时已晚;在整个浇水循环中我需要用户控制。
我也不希望程序在按下 EXIT 时退出,相反,我希望 GPIO 循环在 "OFF" 值处停止并基本上将程序重置为开始状态。
我知道time.sleep()
很粗糙,但我还没有找到其他方法。我见过人们谈论 'threading',我不知道那是什么意思,听起来我永远也不会。我还看到人们谈论 TKinters 的 "after()" 功能,同样,我需要非常清楚的例子来回答。只是说 "Use threading" 没有任何帮助 - 我是菜鸟!
from tkinter import *
import RPi.GPIO as GPIO
import time
master = Tk()
def onoffcycle():
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(14, GPIO.OUT)
GPIO.setup(14, GPIO.LOW)
y=off.get()
y=float(y)
x=on.get()
x=float(x)
GPIO.output(14, True)
print("On")+str(x)
time.sleep(x)
GPIO.output(14, False)
print("Off")+str(y)
time.sleep(y)
def start():
print("Prepare to water in 10 seconds...")
time.sleep(10)
z=cycle.get()
z=float(z)
while z > 0:
onoffcycle()
z=z-1
print("Cycles remaining:")+str(z)
###tog()
###if t==1:
###reset()
else:
reset()
### this program runs onoffcycle() for z number of cycles as set by the slider.
### It should check for an exit toggle, then run the program for one cycle,
### then check for exit, then another round. countdown from cycle.get variable
def reset():
GPIO.cleanup()
on.set(0)
off.set(0)
cycle.set(0)
t=IntVar()
###def tog():
### HELP! This is where I need the program to be looking for the EXIT button being pressed
### in order to stop the program. Everything I have tried so far waits until the z value drops
### to zero, basically until the program stops on its own. I need a button that interrupts
### everything.
on = Scale(master, label="Set # Seconds Water ON:", from_=0, to=180, orient=HORIZONTAL, length=400, width=35,
troughcolor="red", bg="SteelBlue1", fg="black", bd=6, sliderlength=90, sliderrelief=RIDGE,
font = '-weight bold')
on.grid(column=1, row=1, columnspan=3)
off = Scale(master, label="Set # Seconds Water OFF:", from_=0, to=30, orient=HORIZONTAL, length=400, width=35,
troughcolor="yellow", bg="SteelBlue1", fg="black", bd=6, sliderlength=90, sliderrelief=RIDGE,
font = '-weight bold')
off.grid(column=1, row=2, columnspan=3)
cycle = Scale(master, label="Set # of Plants to Water:", from_=0, to=200, orient=HORIZONTAL, length=400, width=35,
troughcolor="green", bg="SteelBlue1", fg="black", bd=6, sliderlength=90, sliderrelief=RIDGE,
font = '-weight bold')
cycle.grid(column=1, row=3, columnspan=3)
go = Button(master, text="START", command=start, bg="SteelBlue3", width=10, height=2, font='-weight bold')
go.grid(column=1, row=4)
adios = Checkbutton(master, text="EXIT", variable=t, indicatoron=0, bd=6, bg="SteelBlue3", width=11, height=2, font='-weight bold')
adios.grid(column=3, row=4)
###adios should have a command to run an exit program - with toggle values? or something else?
resetbutton = Button(master, text="RESET", command=reset, bg="SteelBlue3", width=10, height=2, font='-weight bold')
resetbutton.grid(column=2, row=4)
mainloop()
使用布尔变量True/False
控制循环和其他元素
例如创建全局变量
time_to_exit = False
并在按下 EXIT
按钮时设置 True
并在函数中使用
while z > 0 and not time_to_exit:
它应该停止循环。
您可以使用其他布尔变量来通知程序循环是否已停止并可以退出。
# inform function to use global variable
global loop_is_working
loop_is_working = True
while z > 0 and not time_to_exit:
# ...
def reset():
# inform function to use global variable
global loop_is_working
loop_is_working = False
def on_exit:
if loop_is_working:
reset()
# check again afte 100ms
after(100, on_exit)
else:
# stop `mainloop()` and close tkinter window
root.destroy()
你的另一个问题是:你使用 sleep()
和 while
所以 Tkinter
必须等到它结束。
你可以使用master.after(miliseconds, function_name)
,Tkinter/mainloop
会执行这个函数,同时它会检查你的按钮。
例子
time_to_exit = False
def start():
print("Prepare to water in 10 seconds...")
# mainloop will run `start_loop` after `10s`
master.after(10000, start_loop) # 10s = 10 000ms
def start_loop():
z = float(cycle.get())
loop(z)
# or
#master.after(0, loop, z)
def loop(z):
if z > 0 and not time_to_exit:
onoffcycle()
z -= 1
print("Cycles remaining:", z)
###tog()
###if t==1:
###reset()
# mainloop will run `loop` again as soon as possible
master.after(0, loop, z)
else:
reset()
甚至
# create global variables
time_to_exit = False
loop_is_working = False
def start():
# inform function to use global variable
global loop_is_working
loop_is_working = True
print("Prepare to water in 10 seconds...")
# mainloop will run `start_loop` after `10s`
master.after(10000, start_loop) # 10s = 10 000ms
def start_loop():
z = float(cycle.get())
loop(z)
# or
#master.after(0, loop, z)
def loop(z):
if z > 0 and not time_to_exit and loop_is_working:
onoffcycle()
z -= 1
print("Cycles remaining:", z)
###tog()
###if t==1:
###reset()
# mainloop will run `loop` again as soon as possible
master.after(0, loop, z)
else:
reset()
def reset():
# inform function to use global variable
global loop_is_working
# ... your code ...
loop_is_working = False
这是包含 furas 出色建议的新代码。我还发现了如何让 z
值随着每次迭代而减少。 RESET 按钮现在将在完成当前循环后停止程序,并等待您开始新的循环。这是因为我仍然有 time.sleep()
处理 ON 和 OFF 时间。我尝试为此使用 after()
但它一直导致其他更大的问题。而此时,程序在技术上完全可用,所以我不介意它挂一个周期;它不会影响现实世界的应用!我还有其他问题值得他们自己提问,例如配置触摸屏以正确校准,以及在 auto-starting LDXE 后自动启动程序。我计划将所有内容妥善放置在 class()
中。还希望能够在卡在 if:
语句中时更改 Label 小部件文本。谢谢大家!
#!/usr/bin/python
from Tkinter import *
import ttk
import RPi.GPIO as GPIO
import time
master = Tk()
master.title("Obligatory Title")
master.attributes("-fullscreen", True)
time_to_exit = False
loop_is_working = False
def onoffcycle():
global t
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
GPIO.output(18, GPIO.LOW)
y=float(off.get())
x=float(on.get())
GPIO.output(18, True)
print("On")+str(x)
t.set("Water ON")
t.get()
time.sleep(x)
GPIO.output(18, False)
print("Off")+str(y)
t.set("Water OFF")
time.sleep(y)
def start():
global loop_is_working
global t
loop_is_working = True
print("Prepare to water in 10 seconds...")
t.set("Prepare to water in 10 seconds...")
master.after(10000, loop)
def loop():
z=float(cycle.get())
global loop_is_working
global time_to_exit
global t
t.set("Now Watering...")
if z > 0 and not time_to_exit and loop_is_working:
onoffcycle()
z=z-1
cycle.set(z)
print("Cycles remaining: ")+ str(z)
master.after(0, loop)
else:
reset()
def reset():
global loop_is_working
GPIO.cleanup()
on.set(0)
off.set(0)
cycle.set(0)
t.set("Welcome! Drag the sliders to set values. Tap left or right of the sliders to fine tune values.")
loop_is_working = False
t = StringVar()
t.set("Welcome! Drag the sliders to set values. Tap left or right of the sliders to fine tune values.")
welcome = Label(master, bg="Steelblue3", bd=6, relief=RAISED, width=92,
height=2, textvariable=t)
welcome.grid(column=1, row=1, columnspan=3)
on = Scale(master, label="Set # Seconds Water ON:", from_=0, to=120, orient=HORIZONTAL, length=735,
width=50, troughcolor="steelblue", bg="SteelBlue1", fg="black", bd=6, sliderlength=90,
sliderrelief=RIDGE)
on.grid(column=1, row=2, columnspan=3)
off = Scale(master, label="Set # Seconds Water OFF:", from_=0, to=30, orient=HORIZONTAL, length=735,
width=50,troughcolor="steelblue", bg="SteelBlue1", fg="black", bd=6, sliderlength=90,
sliderrelief=RIDGE)
off.grid(column=1, row=3, columnspan=3)
cycle = Scale(master, label="Set # of Plants to Water:", from_=0, to=200, orient=HORIZONTAL, length=735,
width=50,troughcolor="steelblue", bg="SteelBlue1", fg="black", bd=6, sliderlength=90,
sliderrelief=RIDGE)
cycle.grid(column=1, row=4, columnspan=3)
go = Button(master, text="START", command=start, bg="green3", width=19, height=3,
bd=4, font='-weight bold')
go.grid(column=1, row=5)
adios = Button(master, text="EXIT", command=exit, bg="red3", width=19, height=3,
bd=4, font='-weight bold')
adios.grid(column=3, row=5)
resetb = Button(master, text="RESET", command=reset, bg="yellow3", width=19,
height=3, bd=4, font='-weight bold')
resetb.grid(column=2, row=5)
mainloop()
这是我正在努力使用的程序的第二个版本。我在程序的第一个版本中发布了一个较早的问题,但没有成功。
这是我的 "Big Picture Idea" 以防它帮助您找到我偏离轨道的地方。我有很多植物要浇水,我想用 TKinter 制作一个 GUI,它将出现在 Raspberry Pi 的触摸屏上。 Pi 控制一个连接到水泵的 110V 继电器开关。它只是基本的 "on/off" 循环,用于 z # 株植物。
用户可以使用三个滑块来调整 X、Y 和 Z 值。 X 是水泵需要打开多长时间,Y 是水泵需要关闭多长时间(这样您就有时间将软管移动到下一个工厂),Z 是您必须关闭的工厂总数水。我曾经只是让循环 运行 永远,因为我总是可以 CTRL-C 结束程序,但现在我试图在不需要键盘的情况下在触摸屏上完成它 - 比教别人更用户友好谁不知道 Linux 意味着如何使用终端。它还需要 运行 像人们习惯看到的程序一样,因此需要 GUI 触摸屏按钮(如智能手机和平板电脑)。
该代码大部分运行良好,停止和启动时不会崩溃。我找到了开始按钮,还有一个重置按钮可以清除滑块值,以防您想从零开始重新开始这些值。但似乎影响这里很多人的大问题是该死的“退出”按钮。
我需要用户能够在循环期间随时按下 TKinter EXIT 按钮,并让程序知道何时按下按钮并立即服从它,基本上是中断循环。我尝试了很多东西,没有任何效果。 Checkbuttons 只会等到循环结束,然后更新切换值。那时为时已晚;在整个浇水循环中我需要用户控制。
我也不希望程序在按下 EXIT 时退出,相反,我希望 GPIO 循环在 "OFF" 值处停止并基本上将程序重置为开始状态。
我知道time.sleep()
很粗糙,但我还没有找到其他方法。我见过人们谈论 'threading',我不知道那是什么意思,听起来我永远也不会。我还看到人们谈论 TKinters 的 "after()" 功能,同样,我需要非常清楚的例子来回答。只是说 "Use threading" 没有任何帮助 - 我是菜鸟!
from tkinter import *
import RPi.GPIO as GPIO
import time
master = Tk()
def onoffcycle():
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(14, GPIO.OUT)
GPIO.setup(14, GPIO.LOW)
y=off.get()
y=float(y)
x=on.get()
x=float(x)
GPIO.output(14, True)
print("On")+str(x)
time.sleep(x)
GPIO.output(14, False)
print("Off")+str(y)
time.sleep(y)
def start():
print("Prepare to water in 10 seconds...")
time.sleep(10)
z=cycle.get()
z=float(z)
while z > 0:
onoffcycle()
z=z-1
print("Cycles remaining:")+str(z)
###tog()
###if t==1:
###reset()
else:
reset()
### this program runs onoffcycle() for z number of cycles as set by the slider.
### It should check for an exit toggle, then run the program for one cycle,
### then check for exit, then another round. countdown from cycle.get variable
def reset():
GPIO.cleanup()
on.set(0)
off.set(0)
cycle.set(0)
t=IntVar()
###def tog():
### HELP! This is where I need the program to be looking for the EXIT button being pressed
### in order to stop the program. Everything I have tried so far waits until the z value drops
### to zero, basically until the program stops on its own. I need a button that interrupts
### everything.
on = Scale(master, label="Set # Seconds Water ON:", from_=0, to=180, orient=HORIZONTAL, length=400, width=35,
troughcolor="red", bg="SteelBlue1", fg="black", bd=6, sliderlength=90, sliderrelief=RIDGE,
font = '-weight bold')
on.grid(column=1, row=1, columnspan=3)
off = Scale(master, label="Set # Seconds Water OFF:", from_=0, to=30, orient=HORIZONTAL, length=400, width=35,
troughcolor="yellow", bg="SteelBlue1", fg="black", bd=6, sliderlength=90, sliderrelief=RIDGE,
font = '-weight bold')
off.grid(column=1, row=2, columnspan=3)
cycle = Scale(master, label="Set # of Plants to Water:", from_=0, to=200, orient=HORIZONTAL, length=400, width=35,
troughcolor="green", bg="SteelBlue1", fg="black", bd=6, sliderlength=90, sliderrelief=RIDGE,
font = '-weight bold')
cycle.grid(column=1, row=3, columnspan=3)
go = Button(master, text="START", command=start, bg="SteelBlue3", width=10, height=2, font='-weight bold')
go.grid(column=1, row=4)
adios = Checkbutton(master, text="EXIT", variable=t, indicatoron=0, bd=6, bg="SteelBlue3", width=11, height=2, font='-weight bold')
adios.grid(column=3, row=4)
###adios should have a command to run an exit program - with toggle values? or something else?
resetbutton = Button(master, text="RESET", command=reset, bg="SteelBlue3", width=10, height=2, font='-weight bold')
resetbutton.grid(column=2, row=4)
mainloop()
使用布尔变量True/False
控制循环和其他元素
例如创建全局变量
time_to_exit = False
并在按下 EXIT
按钮时设置 True
并在函数中使用
while z > 0 and not time_to_exit:
它应该停止循环。
您可以使用其他布尔变量来通知程序循环是否已停止并可以退出。
# inform function to use global variable
global loop_is_working
loop_is_working = True
while z > 0 and not time_to_exit:
# ...
def reset():
# inform function to use global variable
global loop_is_working
loop_is_working = False
def on_exit:
if loop_is_working:
reset()
# check again afte 100ms
after(100, on_exit)
else:
# stop `mainloop()` and close tkinter window
root.destroy()
你的另一个问题是:你使用 sleep()
和 while
所以 Tkinter
必须等到它结束。
你可以使用master.after(miliseconds, function_name)
,Tkinter/mainloop
会执行这个函数,同时它会检查你的按钮。
例子
time_to_exit = False
def start():
print("Prepare to water in 10 seconds...")
# mainloop will run `start_loop` after `10s`
master.after(10000, start_loop) # 10s = 10 000ms
def start_loop():
z = float(cycle.get())
loop(z)
# or
#master.after(0, loop, z)
def loop(z):
if z > 0 and not time_to_exit:
onoffcycle()
z -= 1
print("Cycles remaining:", z)
###tog()
###if t==1:
###reset()
# mainloop will run `loop` again as soon as possible
master.after(0, loop, z)
else:
reset()
甚至
# create global variables
time_to_exit = False
loop_is_working = False
def start():
# inform function to use global variable
global loop_is_working
loop_is_working = True
print("Prepare to water in 10 seconds...")
# mainloop will run `start_loop` after `10s`
master.after(10000, start_loop) # 10s = 10 000ms
def start_loop():
z = float(cycle.get())
loop(z)
# or
#master.after(0, loop, z)
def loop(z):
if z > 0 and not time_to_exit and loop_is_working:
onoffcycle()
z -= 1
print("Cycles remaining:", z)
###tog()
###if t==1:
###reset()
# mainloop will run `loop` again as soon as possible
master.after(0, loop, z)
else:
reset()
def reset():
# inform function to use global variable
global loop_is_working
# ... your code ...
loop_is_working = False
这是包含 furas 出色建议的新代码。我还发现了如何让 z
值随着每次迭代而减少。 RESET 按钮现在将在完成当前循环后停止程序,并等待您开始新的循环。这是因为我仍然有 time.sleep()
处理 ON 和 OFF 时间。我尝试为此使用 after()
但它一直导致其他更大的问题。而此时,程序在技术上完全可用,所以我不介意它挂一个周期;它不会影响现实世界的应用!我还有其他问题值得他们自己提问,例如配置触摸屏以正确校准,以及在 auto-starting LDXE 后自动启动程序。我计划将所有内容妥善放置在 class()
中。还希望能够在卡在 if:
语句中时更改 Label 小部件文本。谢谢大家!
#!/usr/bin/python
from Tkinter import *
import ttk
import RPi.GPIO as GPIO
import time
master = Tk()
master.title("Obligatory Title")
master.attributes("-fullscreen", True)
time_to_exit = False
loop_is_working = False
def onoffcycle():
global t
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
GPIO.output(18, GPIO.LOW)
y=float(off.get())
x=float(on.get())
GPIO.output(18, True)
print("On")+str(x)
t.set("Water ON")
t.get()
time.sleep(x)
GPIO.output(18, False)
print("Off")+str(y)
t.set("Water OFF")
time.sleep(y)
def start():
global loop_is_working
global t
loop_is_working = True
print("Prepare to water in 10 seconds...")
t.set("Prepare to water in 10 seconds...")
master.after(10000, loop)
def loop():
z=float(cycle.get())
global loop_is_working
global time_to_exit
global t
t.set("Now Watering...")
if z > 0 and not time_to_exit and loop_is_working:
onoffcycle()
z=z-1
cycle.set(z)
print("Cycles remaining: ")+ str(z)
master.after(0, loop)
else:
reset()
def reset():
global loop_is_working
GPIO.cleanup()
on.set(0)
off.set(0)
cycle.set(0)
t.set("Welcome! Drag the sliders to set values. Tap left or right of the sliders to fine tune values.")
loop_is_working = False
t = StringVar()
t.set("Welcome! Drag the sliders to set values. Tap left or right of the sliders to fine tune values.")
welcome = Label(master, bg="Steelblue3", bd=6, relief=RAISED, width=92,
height=2, textvariable=t)
welcome.grid(column=1, row=1, columnspan=3)
on = Scale(master, label="Set # Seconds Water ON:", from_=0, to=120, orient=HORIZONTAL, length=735,
width=50, troughcolor="steelblue", bg="SteelBlue1", fg="black", bd=6, sliderlength=90,
sliderrelief=RIDGE)
on.grid(column=1, row=2, columnspan=3)
off = Scale(master, label="Set # Seconds Water OFF:", from_=0, to=30, orient=HORIZONTAL, length=735,
width=50,troughcolor="steelblue", bg="SteelBlue1", fg="black", bd=6, sliderlength=90,
sliderrelief=RIDGE)
off.grid(column=1, row=3, columnspan=3)
cycle = Scale(master, label="Set # of Plants to Water:", from_=0, to=200, orient=HORIZONTAL, length=735,
width=50,troughcolor="steelblue", bg="SteelBlue1", fg="black", bd=6, sliderlength=90,
sliderrelief=RIDGE)
cycle.grid(column=1, row=4, columnspan=3)
go = Button(master, text="START", command=start, bg="green3", width=19, height=3,
bd=4, font='-weight bold')
go.grid(column=1, row=5)
adios = Button(master, text="EXIT", command=exit, bg="red3", width=19, height=3,
bd=4, font='-weight bold')
adios.grid(column=3, row=5)
resetb = Button(master, text="RESET", command=reset, bg="yellow3", width=19,
height=3, bd=4, font='-weight bold')
resetb.grid(column=2, row=5)
mainloop()