7

I have a simple Python script that is observing two GPIO pins, I've set the bouncetime parameter to 500ms, but I get multiple (almost always 2) calls to my callback, usually 3 or 4 ms apart.

This is my first Python script, so I may be missing some subtlety other than the GPIO handling.

#!/usr/bin/env python2.7

import os, datetime, atexit, time, urllib2,sys
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)

GPIO.setup(17, GPIO.IN)
GPIO.setup(27, GPIO.IN)

def gate_moving_callback(channel): 

    if GPIO.input(channel):
        print(str(datetime.datetime.now()), " Gate closing")
        urllib2.urlopen("http://<redacted>/prog/GateClose").read()
        sys.stdout.flush()
    else:
        print(str(datetime.datetime.now()), " Gate opening")
        urllib2.urlopen("http://<redacted>/prog/GateOpen").read()
        os.system('./gatepic')
        sys.stdout.flush()

def shutdown_request(channel):
    print "change in GPIO27: ", channel
    print GPIO.input(channel)
    if GPIO.input(channel):
        print str(datetime.datetime.now()), " Shutdown request"
        sys.stdout.flush()
        os.system('/sbin/shutdown -h now')

def cleanup():
    #print("Cleaning up")
    GPIO.cleanup()

atexit.register(cleanup)

GPIO.add_event_detect(17, GPIO.BOTH, callback=gate_moving_callback, bouncetime=500)
GPIO.add_event_detect(27, GPIO.BOTH, callback=shutdown_request, bouncetime=500)

# Wait indefinitely...
print str(datetime.datetime.now()), " Ready!"
sys.stdout.flush()

while True:
    time.sleep(1);

Here's a fairly typical example from the log:

('2014-03-02 12:26:42.196143', ' Gate opening')
('2014-03-02 12:26:42.200036', ' Gate opening')
('2014-03-02 12:27:49.946151', ' Gate closing')

Note that the second gate opening message is only 4ms after the first, even though I've specified bouncetime=500.

It always reports two opens and one close.

6 Answers 6

7

The first thing to check in mysterious situations like this is do you have the latest version of the GPIO library? The newest one should be 0.5.4.

The second problem may be that you're not setting a pull-up or a pull-down. In an electrical circuit with only a switch, when the switch is closed, you know what it is. However, when the switch is open there's no physical electrical connection and so technically the Pi could read anything on the GPIO pin. (What it actually reads is some weird combination of electro-magnetic radiation from your refrigerator and static electricity from you sweater, that sort of thing.) To prevent the "unknown" state, a pull-up or pull-down resistor is used. This way, when the switch is open, the resistor comes into play and the circuit is still defined.

If the switch is connected to ground and the GPIO pin, you want a pull-up resistor. By default it "pulls the circuit up" by connecting the pin to +3.3 V, so that the pin reads 1 unless the switch is closed. The swtich has no resistance, so it short-circuits (in a good way!) the resistor and the pin now reads 0. The switch opens back up, the resistor comes back into play, and all is well reading 1 again. A pull-down resistor works the same, but exactly the opposite. It connects the pin to ground, so that the pin always reads 0. The switch then connects the pin to +3.3 V. When the switch is closed, the voltage short-circuits the ground connection and the pin reads 1. Etc.

The resistors can be added in one of two ways. A physical resistor can be put in place, or the Pi has internal resistors that can be used. The GPIO library has support for this. For example:

GPIO.setup(PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)

This activates a pull up, and my switch on PIN is connected to ground.

If that doesn't fix it, you may be hitting a weird edge case in the code with threading that I wouldn't have though possible on a single-cpu system. You might need to implement your own software check for debounce or to try adding a small capacitor (0.1 uF) across the switch. A further increase of bouncetime might help, but if it doesn't I'd suspect bug.

5
  • Thanks for the reminder on checking the library version; upgraded from 5.2a to 5.4, but no difference.
    – TomG
    Mar 4, 2014 at 2:20
  • I do have a pull-up resistor, and I don't get any false triggers, except for the second one 5-15ms after the first. I find it curious that I always get two calls from the open, and only one from the close. I have not seen it vary from this pattern yet.
    – TomG
    Mar 4, 2014 at 2:23
  • I've tried your code and it works perfectly for me, once I add the pull_up_down option. All I can suggest at this point is your getting some jitter in the electrical signal that's causing it. I'd try activating the internal resistor even if you have an external one at this point.
    – Fred
    Mar 4, 2014 at 7:09
  • Unfortunately, the adding the internal pull up didn't help. I'm ready to give in and limit it to one activation per second.
    – TomG
    Mar 7, 2014 at 1:54
  • Darn. It may be best in that case to disable the bouncetime and implement it manually, since that's essentially what you're doing anyway.
    – Fred
    Mar 7, 2014 at 7:43
4

I've had the very same problem for a long time and I couldn't figure out what the problem was.

What seemed to have fixed it for me (it's a weird fix, I'll say so myself...) in addition to the above, having a pull_up_down=GPIO.PUD_UP in your GPIO.setup was adding a small delay after the event is detected, and before you check which one, like so:

time.sleep(0.25)
if GPIO.input(channel):
    print(str(datetime.datetime.now()), " Gate closing")
    urllib2.urlopen("http://<redacted>/prog/GateClose").read()
    sys.stdout.flush()

I can't explain why this would work, but it does. I suspect that eventhough an event is detected, the system needs a little bit more time before it knows which one it is, rising or falling, or that it's two systems and one is slower to catch on that the other? I don't know...

Give it a try!

3
  • Thanx, that solved it for me too!
    – mstuebner
    Nov 3, 2016 at 20:35
  • This works, thanks. But it would be nice if somebody could explain it.
    – Conffusion
    Sep 15, 2019 at 19:17
  • This only leads to the multiple events being delayed. But the callback is still called 2-3 times per event for me. With pull-up and bouncetime=200.
    – ygoe
    Nov 15, 2022 at 15:08
2

I think you should change GPIO.BOTH to either GPIO.RISING or GPIO.FALLING. I suspect that any jitter otherwise could be detected as two different interrupt event, one falling and one rising.

Regards

2
  • That's a great point. I'll try separate events for GPIO.RISING and GPIO.FALLING as I do want to know both when the gate opens and closes.
    – TomG
    Jul 19, 2014 at 0:35
  • Beware that you cannot add a rising and a falling callback for the same pin. You have to use "both" and then read out the GPIO in the callback, which gives outdated results at best. RPi.GPIO is just not usable for this kind of work, it has a bad design here.
    – ygoe
    Nov 15, 2022 at 15:10
2

Following code worked for me. Prettey the same version as by "joshuar500", but with callback using:

def setup():
    GPIO.add_event_detect(button, GPIO.RISING, callback=button_callback,
                          bouncetime=500)  # Setup event pin "button" rising edge

def button_callback(channel):
    sleep(0.1)
    GPIO.remove_event_detect(button)
    do_something()
    sleep(0.1)
    setup()
1
  • Worked for me as well. Awesome <3
    – Axel
    Jan 28, 2021 at 15:12
1

I had a similar situation where I was receiving multiple callbacks.

Instead of setting up a callback, I just create a loop that basically waited for the button to be pushed. When it was pushed, I removed the button from the event handler, waited a bit, then re-added it.

It looked something like this:

def setup():
    GPIO.add_event_detect(btn, GPIO.RISING, bouncetime=1)

def loop():
    while True:
        time.sleep(0.25)
            if GPIO.event_detected(btn):
                GPIO.remove_event_detect(btn)
                do_something()
                time.sleep(1)
                GPIO.add_event_detect(btn, GPIO.RISING, bouncetime=1)

Hope this helps anyone else who was getting multiple callbacks for one button click.

0

I got the same issue, and couldn't fix it while using GPIO.BOTH, so instead I used both GPIO.RISING and GPIO.FALLING:

# variable to keep track of button's last event:
self.btn_fell = False

# init with FALLING event:
GPIO.setup(btn, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.add_event_detect(btn, GPIO.FALLING, callback=lambda x: self.interupt_btn(), bouncetime=50)

# then in the interrupt, I switch the type of event:
def interupt_btn(self):
    self.btn_fell = not self.btn_fell
    if self.btn_fell:
        # do your stuff on press
        GPIO.remove_event_detect(btn)
        GPIO.add_event_detect(btn, GPIO.RISING, callback=lambda x: self.interupt_btn(), bouncetime=50)
    else:
        # do your stuff on release
        GPIO.remove_event_detect(btn)
        GPIO.add_event_detect(btn, GPIO.FALLING, callback=lambda x: self.interupt_btn(),bouncetime=50)

A bit long, but it works perfectly and no need to waste time sleeping, which was important for my application. Hope that can help someone! ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.