Python RaspberryPi GPIO 事件检测在 Tkinter 中失败
我在用Python和Tkinter在树莓派上检测GPIO事件时遇到了一个奇怪的问题。
当我点击startGameButton
按钮时,它会调用start_game
这个函数。在这个函数里,我在try
块中添加了一个GPIO事件,并且有一个循环会运行30秒。在这段时间里,我期待在23号引脚上会发生GPIO.Falling
事件,每当这个事件发生时,p2ScoreEvent
函数应该会被执行。实际上发生的情况是,这个事件似乎只在第一次发生时被触发,如果我不断制造GPIO.Falling
事件,直到循环结束都不会有任何反应。循环结束后,如果事件发生了多次,p2ScoreEvent
函数会被调用第二次,但就只有这么一次。
一旦我走出start_game
中的那个循环,事件检测就完全正常了。我还确认了这一部分:
try:
GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)
while (time.time() - start_time) < game_time
print "listening"
time.sleep(5)
except:
print "Something went wrong..."
GPIO.cleanup()
在命令行中单独运行时是正常的。
这是让我遇到问题的完整代码片段:
from Tkinter import *
import time
import RPi.GPIO as GPIO
class App:
def p2ScoreEvent(self, p1pin):
print "ScoreEvent"
global p2score
p2score = p2score + 1
p2diag.set(p2diagString + repr(p2score))
def start_game(self):
global p2score
start_time = time.time()
game_time = 30 #length of game
P1PIN = 23
GPIO.setmode(GPIO.BCM)
GPIO.setup(P1PIN, GPIO.IN)
GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)
try:
GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)
while (time.time() - start_time) < game_time
print "listening"
time.sleep(5)
except:
print "Something went wrong..."
GPIO.cleanup()
def __init__(self, master):
frame = Frame(master)
global p2diagString
p2diagString = "Player 2 Score: "
global p2score
p2score = 0
global p2diag
p2diag = StringVar()
p2diag.set(p2diagString + repr(p2score))
p2Label = Label(root, fg="white", bg="blue", textvariable=p2diag).grid(row=1, column=1)
self.startGameButton = Button(
root, text="Start Game!", command=self.start_game
)
self.startGameButton.grid(row=3, columnspan=2)
root = Tk()
app = App(root)
root.mainloop()
我在想这可能和调用start_game
函数有关,但我不太确定具体是什么。我对Python的经验不多,所以有点难以理解到底发生了什么。
为什么GPIO事件只在第一次发生时被触发,而如果它实际上发生了超过两次,为什么在循环结束时只触发一次?
1 个回答
mainloop()
是程序中负责处理所有工作的部分 - 它会一个接一个地运行事件函数(以及其他函数),看起来就像是在同时处理多个任务。但如果有哪个函数运行得太久(比如使用了 while True
或 time.sleep()
),那么 mainloop
就无法执行其他函数了。
所以,不要使用 time.sleep()
和长时间运行的循环,而是应该使用 root.after(time, function)
来重复运行某个函数。
我不能测试这个,但它可能看起来像这样:
def my_loop(self):
if (time.time() - self.start_time) < self.game_time:
print "listening"
root.after(5000, self.my_loop) # run my_loop again after 5000 milliseconds
def start_game(self):
global p2score
# use self. to get access in other function
self.start_time = time.time()
self.game_time = 30 #length of game
P1PIN = 23
GPIO.setmode(GPIO.BCM)
GPIO.setup(P1PIN, GPIO.IN)
GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)
try:
GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)
self.my_loop() # run my_loop first time
except:
print "Something went wrong..."
GPIO.cleanup()
顺便说一下:
你可以使用 self.end_time
来减少计算量。
def my_loop(self):
if time.time() < self.end_time:
print "listening"
root.after(5000, self.my_loop) # run my_loop again after 5000 milliseconds
def start_game(self):
global p2score
# use self. to get access in other function
# self.game_time = 30
self.end_time = time.time() + 30
顺便说一下:
我们使用 classes
和 self.
来避免使用 global
。
所有代码可能看起来像这样:
from Tkinter import *
import time
import RPi.GPIO as GPIO
class App():
def __init__(self, master):
self.master = master
self.frame = Frame(master)
self.p2diagString = "Player 2 Score: "
self.p2score = 0
self.p2diag = StringVar()
self.p2diag.set(self.p2diagString + str(self.p2score))
p2Label = Label(self.frame, fg="white", bg="blue", textvariable=self.p2diag)
p2Label.grid(row=1, column=1)
self.startGameButton = Button(
self.frame, text="Start Game!", command=self.start_game
)
self.startGameButton.grid(row=3, columnspan=2)
def p2ScoreEvent(self, p1pin):
print "ScoreEvent"
self.p2score += 1
self.p2diag.set(self.p2diagString + str(self.p2score))
def start_game(self):
self.game_time = 30
self.end_time = time.time() + self.game_time
P1PIN = 23
GPIO.setmode(GPIO.BCM)
GPIO.setup(P1PIN, GPIO.IN)
GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)
try:
GPIO.add_event_detect(P1PIN, GPIO.FALLING, callback=self.p2ScoreEvent)
self.my_loop()
except:
print "Something went wrong..."
GPIO.cleanup()
def my_loop(self):
if time.time() < self.end_time:
print "listening"
root.after(5000, self.my_loop) # run my_loop again after 5000 milliseconds
def run(self):
self.master.mainloop()
#----------------------------------------------------------------------
App(Tk()).run()
我把 __init__
放在类的第一个函数位置 - 这样更容易阅读 - 大家都习惯在类的开头看到 __init__
。
我用 str()
替代了 repl()
。
在类里面,我不使用外部变量,所有变量都放在内部。