1

On my Raspberry Pi using python3 I want to count pulses on an incoming I/O. To test I've put together a simple program.

import signal
import sys
import RPi.GPIO as GPIO
import time

iTP = 40
ictr=0

def signal_handler(sig, frame):
    GPIO.cleanup()
    print("\nOut of here!")
    sys.exit(0)

def button_pressed_callback(channel):
    global ictr
    ictr+=1

def main():
    print("Here we go!")
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(iTP, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.add_event_detect(iTP, GPIO.FALLING, 
            callback=button_pressed_callback, bouncetime=20)
    
    signal.signal(signal.SIGINT, signal_handler)
    
    sctr=0
    ictr=1
    while 1:
        time.sleep(1)
        print("====")
        while sctr==ictr:
            time.sleep(1)
        print("busy")
        
        while sctr!=ictr:
            time.sleep(2)
        print("Cnt={}".format(sctr))
        ictr=0
        sctr=0
    
main()

I want to collect a pulse count in the callback and then deal with the count in main(). The pulses are in fact being collected in the callback - i.e. ictr is incrementing as expected - but ictr in main() is not the reflecting this. I never get past the print("busy") line even though ictr is changing in the callback (verified with prints). I have tried a variety of combinations of global declarations but just can't seem to get this variable to be shared between the callback and main(). Would greatly appreciate any help.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
  • It looks like the problem is that you're [shadowing](https://en.wikipedia.org/wiki/Variable_shadowing) `ictr` in `main()`. Try removing the definition in `main()` and see if it works. I'd test it for myself, but this isn't a [mre]. I'm not sure if concurrency is an additional factor (and I don't do much async work myself so I can't guess). – wjandrea Aug 16 '21 at 05:43

2 Answers2

0

The moment you wrote ictr=1 in main without first declaring it to be global, you made it into a local variable. You have two options:

  1. Initialize it elsewhere, and remove ictr=0 from the loop, so as not to make it local to main. This will make main look up the name ictr via LEGB rules, which will stop at G: global.

  2. Declare global ictr in main as you did in button_pressed_callback.

The second option seems more practical in this case. A better design choice might be to use a class to encapsulate the namespace that is currently global.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • I tried what you suggest in 1 above. I tried about 6 different variations using the global and the nonlocal keywords. I moved the callback into main and tried each variation. In every case I either get an error of some kind or the program hangs at the print("busy") line indicating I think that the two instances of ictr (callback and main) are in different scopes. – user2542266 Aug 16 '21 at 17:45
  • So adding `global ictr` as the first line of `main` didn't help? I find that odd – Mad Physicist Aug 16 '21 at 18:43
  • I don't think option 1 is practical for you: you need to be able to assign to `ictr` in `main` – Mad Physicist Aug 16 '21 at 18:44
  • The problem is that the callback function is apparently running in different thread. I know I can print in that thread but I wonder how else I can communicate with the main thread. – user2542266 Aug 16 '21 at 19:01
  • @user2542266. Typically you would set up a Queue or something like that. – Mad Physicist Aug 16 '21 at 20:57
  • @user2542266. Probably a different process, according to https://stackoverflow.com/q/17774768/2988730 – Mad Physicist Aug 16 '21 at 21:00
  • A queue would work if I set up my own class/thread. In this case i have no way to identify the queue to the callback w/o having the same scope issue. – user2542266 Aug 16 '21 at 21:52
  • @user2542266. Try using `https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Value`: `ictr = Value(int); ictr.value = 0` to initialize, `with ictr.lock(): ictr.value += 1` to increment, etc. – Mad Physicist Aug 16 '21 at 21:55
  • Still weird that `global ictr` in `main` didn't work based on your answer. – Mad Physicist Aug 16 '21 at 21:57
0

With info I found in various places I was able to create and use the following code successfully. It's not clear to me why I don't have the same scope issues here but this works just fine.

import RPi.GPIO as GPIO

class Handle_Interrupts:

    intCnt=0
    
    def __init__(self,ipin, bouncetime):
        self.ipin=ipin
        self.bouncetime=bouncetime

        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(self.ipin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        GPIO.add_event_detect(self.ipin, GPIO.FALLING, 
            self.int__callback, self.bouncetime)

    def int__callback(self,channel):
        self.intCnt+=1
    
    def getCnt(self):
        return(self.intCnt)
        
    def clrCnt(self):
        t=self.intCnt
        self.intCnt=0
        return(t)