dubyaa
dubyaa

Reputation: 199

Python RaspberryPi GPIO Event Detection in Tkinter Failing

I'm having a strange problem detecting GPIO events on the Raspberry Pi using Python with Tkinter.

Once the startGameButton is clicked, which calls the start_game function, a GPIO event is added in the try block, and a while loop runs for 30 seconds. During this time, I am expecting the GPIO event, GPIO.Falling on pin 23 to occur and each time that the event occurs, the p2ScoreEvent function should execute. What actually happens is the event seems to only fire the first time that it occurs, if I keep causing the GPIO.Falling event to occur nothing will happen until the while loop completes. Once the loop completes, if the event occured more than once it calls the p2ScoreEvent a second time, but that's it.

Once I'm out of that while loop in start_game the event detection works perfectly. I've also verified that this part:

                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()

functions correctly at the command line when it is not part of a function.

Here's the full code snippet that's giving me issues:

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()

I'm thinking this has something to do with the function call to start_game but I'm not sure exactly. I don't have much python experience, so I'm having a little trouble understanding what exactly is going on.

Why does the GPIO event only occur the first time it happens, and why does it then fire once and only once at the end of the while loop if it actually occurred more than 2 times?

Upvotes: 1

Views: 3540

Answers (1)

furas
furas

Reputation: 142641

mainloop() do all job in program - it runs event function (and other functions) - one after the other - and it looks like multitasking. But if any of function work too long (for example it use while True or time.sleep() then mainloop can execute other functions.

So don't use time sleep() and long running loop but use root.after(time, function) to run some function repeatedly.

I can't test it but it could looks like this:

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()

BTW:

you could use self.end_time to make less calculations

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

BTW:

We use classes and self. to not use global

All code could look like this:

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()

I put __init__ as first function in class - it easer to read it - everybody expect __init__ at the beginning of class.

I use str() in place of repl()

In class I don't use external variables. I have all variables inside.

Upvotes: 1

Related Questions