Python RaspberryPi GPIO Event Detection in Tkinter Failing
Question: 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?


Answer: `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.

