How to use interrupts with Python on the Raspberry Pi and RPi.GPIO – part 2
Interrupts are an efficient way for a program to be able to respond immediately to a specific event. In the previous article I explained the basics of using interrupts in RPi.GPIO and gave an example of a simple “wait for an event” interrupt program.
In this second article I will introduce “threaded callback” which opens up a lot of new possibilities.
Threaded callback – what the heck is that?
I know it sounds complicated. And it probably is complicated in the C code it’s written in, but we’re Pythonites and we don’t have to go there. ;)
If you remember the previous example program was just a simple “wait for port 23 to be connected to GND when we press the button and then print a message and exit the program”.
So, while it was waiting, the program wasn’t doing anything else. The program only had one thread, which means only one thing was being done at once. Python is capable of running more than one thread at once. It’s called multi-threading. It means that you can go through more than one piece of code simultaneously. This is where we can reap the benefit of interrupts because we can do something else while we wait for our “event” to happen. (Just like your “postman detector” allowed you to get on with something else instead of being distracted by waiting for the mail.)
So that covers the threading part of threaded callback. What’s a callback?
When an event is detected in the second thread, it communicates this back to the main thread (calls back). What we now have in RPi.GPIO is the ability to start a new thread for an interrupt and specify a set of instructions (function) that will run when the interrupt occurs in the second thread. This is a threaded callback function.
This is like your “postman detector” giving you a list of reminders of things you wanted to do when your delivery arrives AND doing them for you, so you can carry on with what you want to be doing.
So What are we going to do now?
We’ll keep most of what we did before and add another button and an event detect threaded callback that runs when the new button is pressed, even though we are still waiting for the first button to be pressed.
But this time, the new button will connect GPIO port 24 to 3.3V (3V3) when pressed. This will allow us to demonstrate a rising edge detection. So we’ll be setting up port 24 with the built in pulldown resistor enabled.
Later on we’ll have to modify the code to cope with ‘button bounce’, but we won’t say any more about that just yet.
Do you need to update RPi.GPIO?
If you didn’t do it for the first example, you will quite likely need to update your RPi.GPIO package. You can check what version of RPi.GPIO you have in the command line with…
import RPi.GPIO as GPIO
This should show you what RPi.GPIO version you have. You need 0.5.2a or higher for this example.
You can exit the python environment with
Install RPi.GPIO version 0.5.2 for simple interrupts
If you need to, you can install 0.5.2 or later with
sudo apt-get update (This will update all your Raspbian packages and may take up to an hour)
sudo apt-get dist-upgrade
Update July 2014
The best way to get the latest RPi.GPIO (currently 0.5.5) is to flash a new SD card with the latest NOOBS or Raspbian. This will give you a clean start with the latest version of RPi.GPIO.
And now onto the code
I’ve put most of the explanation in the code, so that if you use it, you will still have it.
#!/usr/bin/env python2.7 # script by Alex Eames https://raspi.tv import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) # GPIO 23 & 24 set up as inputs. One pulled up, the other down. # 23 will go to GND when button pressed and 24 will go to 3V3 (3.3V) # this enables us to demonstrate both rising and falling edge detection GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # now we'll define the threaded callback function # this will run in another thread when our event is detected def my_callback(channel): print "Rising edge detected on port 24 - even though, in the main thread," print "we are still waiting for a falling edge - how cool?\n" print "Make sure you have a button connected so that when pressed" print "it will connect GPIO port 23 (pin 16) to GND (pin 6)\n" print "You will also need a second button connected so that when pressed" print "it will connect GPIO port 24 (pin 18) to 3V3 (pin 1)" raw_input("Press Enter when ready\n>") # The GPIO.add_event_detect() line below set things up so that # when a rising edge is detected on port 24, regardless of whatever # else is happening in the program, the function "my_callback" will be run # It will happen even while the program is waiting for # a falling edge on the other button. GPIO.add_event_detect(24, GPIO.RISING, callback=my_callback) try: print "Waiting for falling edge on port 23" GPIO.wait_for_edge(23, GPIO.FALLING) print "Falling edge detected. Here endeth the second lesson." except KeyboardInterrupt: GPIO.cleanup() # clean up GPIO on CTRL+C exit GPIO.cleanup() # clean up GPIO on normal exit
Two ways to get the above code on your Pi
If you are in the command line on your Pi, type…
Then click “copy to clipboard” (above) and paste into the nano window. Then
Alternatively, you can download this directly to your Pi using…
Then you can run it with…
sudo python interrupt2.py
What’s supposed to happen?
When you run the code it gives you a message “Waiting for falling edge on port 23”
If you press button 1, it will terminate the program as before and give you a message
“Falling edge detected.”
If, instead of button 1, you press button 2, you’ll get a message
“Rising edge detected on port 24”.
This will occur as many times as you press the button. The program is still waiting for the original falling edge on port 23, and it won’t terminate until it gets it. Because your second button press is detected in another thread, it doesn’t affect the main thread.
You may also notice, depending on how cleanly you press the second button, that sometimes you get more than one message for just one button press. This is called “switch bounce”.
Bouncy, bouncy, bouncy
When you press a button switch, the springy contacts may flex and rapidly make and break contact one or more times. This may cause more than one edge detection to trigger, so you may get more than one message for one button press. There is, of course, a way round it, in software.
Why didn’t this happen before?
I hear you ask. The answer is simple. Last time the program was simply waiting for a single button press. As soon as that button press was detected, it stopped waiting. So if the switch bounced, it was ignored. The program had already moved on. In our case, it had closed. But, when the event detection is running constantly in another thread, this is not the case and we actually need to slow things down a bit in what is called “software debouncing”.
How to do software debouncing
In order to debounce, we need to be able to measure time intervals. To do that, we use the time module. Near the top of the program we need to add a line…
import time. I add it immediately after the RPi.GPIO import
import RPi.GPIO as GPIO import time GPIO.setmode(GPIO.BCM)
Then, also quite near the top of the program we need to set an intial value for the variable time_stamp that we will use to measure time intervals
time_stamp = time.time() I put this after the GPIO.setup commands. This sets time_stamp equal to the time in seconds right now.
GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) time_stamp = time.time()
Now we have to change our threaded callback function from
def my_callback(channel): print "Rising edge detected on port 24 - even though, in the main thread," print "we are still waiting for a falling edge - how cool?\n"
def my_callback(channel): global time_stamp # put in to debounce time_now = time.time() if (time_now - time_stamp) >= 0.3: print "Rising edge detected on port 24 - even though, in the main thread," print "we are still waiting for a falling edge - how cool?\n" time_stamp = time_now
And now, even if you deliberately press the button twice, as long as you do it within 0.3 seconds it will only register one button press. You have debounced the switch, by forcing the program to ignore a button press if it occurs less than 0.3 seconds after the previous one.
If you are IDLE (Python joke) you can get the amended code here…
How does this work?
time_now = time.time() stores the current time in seconds in the variable time_now.
Now look at the last line of the function.
time_stamp = time_now This stores the time in seconds when the function was started in a global variable called
time_stamp So next time this function is called, it will be able to check how much time has elapsed since the last time.
That’s what is happening in the if statement.
if (time_now - time_stamp) >= 0.3: In English this means “If more than 0.3 seconds has elapsed since the last button press, execute the code in the indented block”.
So if more than 0.3 seconds have elapsed it would print the message…
“Rising edge detected on port 24 – even though, in the main thread we are still waiting for a falling edge – how cool?”
If less than 0.3 seconds has elapsed, it will do nothing. Job done. Button switch is debounced.
Update – RPi.GPIO 0.5.2 onwards includes this debounce algorithm
Ben has included the above debounce algorithm in 0.5.2 onwards. This article was originally written for 0.5.1. So the above debounce code has been superseded by adding
bouncetime=xxx, where xxx is a time in milliseconds. e.g.
GPIO.add_event_detect(channel, GPIO.RISING, callback=my_callback, bouncetime=200)
I will update the next example to reflect this.
So, what’s next?
- We’ve learnt about simple “wait for” interrupts in the previous article
- We’ve covered threaded callback in this article.
- In the next article, we’ll go over the situation where you want to do more than one threaded callback interrupt at once.
If you can’t wait for the next article (coming soon to a blog near you) check out the documentation here and press on by yourself.
I hope you are enjoying this series. Click here for Part 3.
RasPiO® GPIO Reference Aids
Our sister site RasPiO has three really useful reference products for Raspberry Pi GPIO work...
[…] button press” interrupt. There’s a lot more you can do with them, as I will show you in the next article, which will cover “threaded callback”, which allows us to use the spare capacity we’ve freed up by not polling […]
This is absolutely excellent. I can see a lot of applications for this in remote control motor projects where you want to respond to, say Forward and Left at the same time. Nicely explained :-)
Cheers Mike. Yes it opens up the possibilities quite radically doesn’t it? Of course, you can do all that with polling, but with the Pi we want as much efficiency as we can get.
Bonus question for extra credit. Can anyone tell me why I chose those ports?
Internal pull-up and pull-down Resistors
Good guess, but not the reason. I think all the GPIO have them actually (although the i2c ports have them permanently).
on the Gertboard ports 25 24 23 working together with S1, S2 and S3.
I thank you so much for this tutorial and so may other hints and tipps.
Your English is perfect understandable to me and all is so logical and easy to follow, a very good teacher!
Another very good guess, but not the reason I chose them. ;) I didn’t use the Gertboard for this particular experiment. Thank you for your kind words. :)
I like to use those 2 pins because there is a ground next to each of them. Plus they are right in front the way I have my RPi facing. By default they are not used for any of the communication interfaces, either.
That’s pretty much it Marv – well done. :)
For this test I used button switches scavenged from an old PC (same as in my reset switch video). Each switch is wired to a 2 pin female connector, so it makes it really easy to pick pins on the GPIO header that have a GND next to them (and aren’t used for anything else significant).
I guess that would be more obvious if the fritzing diagram actually showed what all the pins were connected to, rather than listing a bunch of them as “–” :-/
Yeah, I know it’s because they were originally DoNotConnect pins, but that hasn’t been the case now for a long time.
It’s worse than that Andrew, the Fritzing Pi model won’t allow connection to those pins so I had no choice. It wasn’t meant to be an easy one and I’m surprised anyone got it. :) Well done Marv.
OK I know,
It’s because in Fritzing the blue wire looked better horizontal if the yellow was horizontal it would have been CS0 which was no good so you picked the next one to the blue one. :-)
Now tell me am I right
Nope. The Fritzing circuit was done afterwards. OK I’ll give you guys a (not very big) clue. The answer lies on the 26 GPIO pins themselves.
I’ve got an LED hooked up to pin 25, a button to pin 24, and another button to pin 23 (all through the Gertboard). I run the following code:
I hoping that when I press the button on 24 that the LED on 25 will light for a second. It does not. I am stumped as to why this wouldn’t work.
I know I have the LED wired properly, because when I press the button on 23, the LED on 25 lights up, and when release the button on 23, the LED on 25 goes out.
I know that I have the button on 24 wired properly because when I let go of the button, after a second pause, the message prints out.
Any ideas on what I’m doing wrong?
cleanup() at the top? GPIO.cleanup() only cleans up ports opened or used by THIS script. Putting it at the top does nothing.
Your problem appears to be that you’re waiting for a rising edge on 24 but you’ve pulled port 24 high. If it’s already high it can’t rise. It may well work if you change it to falling (but I don’t know how it’s wired).
Try it and see. :)
The cleanup is at the top because I forgot to remove it after I added the keyboardinterrupt exception catch. :)
I know that the program is catching the rising button because of the print statement. It prints out the “buttonRising” each time I press and release the button (sometimes more than once). The sleep even happens (there’s a second pause before the “buttonRising” appears. So I know it’s going into the callback code. It’s just not lighting the LED.
I played with my code, a lot. Changing various settings, and finally got it to work.
import RPi.GPIO as gpio
Glad you got it sorted Rick. Spent most of today travelling. :)
[…] out the second part of the series we shared last week, from Raspi.TV: Interrupts are an efficient way for a program to be able to respond immediately to a specific […]
Fascinating stuff! As a newbie, maybe you can help. Rather than printing to the screen, is there anyway to have the input added to an existing database to record the Time/Date and Duration of the button push?
Well you can certainly write the information to a file, but I haven’t delved into databases more complex than flat files in case you’re talking about MYSQL or something like that.
Thanks again for this, Alex – my Picorder now switches to the next function without having to hold the button down :-)
Way to go. Hope it goes well tomorrow at Harlow. :)
Thanks for the great tutorial! I have it up and running on my pi with great success. I have two questions I was hoping to get help with:
1. Is there any way to detect whether it caught the rising or falling edge in the callback? I would like to either have a callback for falling and another for rising which doesnt seem to be allowed or to have one callback with an if statement to check for rising or falling
2. Is there a way to extend this feature to other buttons? Specifically I would like to use the buttons on an I2C adafruit pi plate with lcd.
1. http://code.google.com/p/raspberry-gpio-python/source/browse/source/py_gpio.c#377 would seem to indicate you can’t have separate callbacks for RISING and FALLING on a single GPIO. I haven’t tested this, but couldn’t you add the callback for BOTH and then just check the current value of the GPIO inside the callback? If the GPIO is now high, it must have been a rising edge; if the GPIO is low it must have been a falling edge… ? *shrug*
2. I guess edge-detection depends on the specifics of the I2C GPIO expander chip that’s being used – check the datasheet. But you won’t be able to use the specific library being used here, https://pypi.python.org/pypi/RPi.GPIO says “Note that the current release does not support SPI, I2C, hardware PWM or serial functionality on the RPi yet.”
Yes Andrew’s 1) above would surely work.
And 2) there’s no native support for i2c in RPi.GPIO (although the chip you specified does have a driver in the forthcoming WiringPi 2)
Well sure I can check the GPIO Pin if you want to take the easy and efficient way :-) No idea why I didn’t think of that.
BTW checking that pin allowed me to have the push and hold condition that I was looking for.
Thanks for the useful subjects you have in your blog!
One observation though. I have spent quite some time trying to handle Ctrl+C properly but I couldn’t figure it out.
Does handling CTRL+c in your code work fine ?
In my case it doesn’t and I suspect after a few tests it’s because the the ctrl+c takes place while we are not in the try: part.
For example when I try this from the console
it works fine. I get the ‘Bye!’
When I try the following from the console
it doesn’t handle the exception properly (I don’t get ‘bye!’ but an unhandled exception).
I think I am hitting the second scenario with your code but not 100% sure
Great stuff nonetheless!
You’re right, it’s because you are hitting CTRL+C before you get to the try: part.
Ok for the record I was wrong. Everything looked well. The system was hanging withing the try: so it should have worked. Not sure why it doesn’t.
After a bit of googling I added
in the try: part and it seems to work now. I don’t know why it doesn’t work with out it (the except part is not getting caught) but that’s more like a python question rather that RPi question. Seems like I am hitting something like the one described here
Just in case sbd else runs into the same issue
Hi thanks a lot for his tutorial, i have a question, is possible to use the GPIO.wait_for_edge(23, GPIO.FALLING) in a while loop with other GPIO.wait_for_edge(24, GPIO.FALLING)?
I think you can only do one wait_for_edge in a thread. Wouldn’t you be better of using callbacks if you want more than one?
Nope, wait_for_edge is a blocking function (i.e. no other code can run at the same time). That’s what the callback functions are for (AFAIK there’s no problem having separate callback functions on different GPIO pins). I guess you could even have a while loop that did nothing but sleep(1).
I think you can have as many callbacks as you want. It was a bit temperamental at first, but I think it’s nicely polished now. I haven’t tested it to a silly extent though. ;)
Thanks for the quick answer anyway i got it working with callbacks.. Here is the code if somebody need it:
import RPi.GPIO as GPIO
from time import sleep
GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# Internal button, to open the gate
print 'Gate opened';
# External button, to ring the bell
print 'Bell rang';
GPIO.add_event_detect(19, GPIO.BOTH, callback=Gate, bouncetime=300)
GPIO.add_event_detect(24, GPIO.BOTH, callback=Bell, bouncetime=300)
# Here everythink to loop normally
Well i can’t understand why but using this script when i turn on any light on my house it run the callback gate
I’m afraid “it doesn’t work properly” is never a very helpful comment – you’ll need to provide full details of your hardware setup (what connections have you wired to which GPIOs, using which resistors, which pullup settings, which Pi model/revision, etc.), your software setup (source code to your full script, which version of RPi.GPIO, which version of Python, etc.), and anything else relevant.
You could also try asking at http://www.raspberrypi.org/phpBB3/ – I don’t want to take anything away from Alex’s excellent blog, but there’s surely many more people reading the RaspberryPi Forums than read RasPI.TV
Ahhh, by chance I’ve just spotted http://www.raspberrypi.org/phpBB3/viewtopic.php?t=50323 where you explain the problem in more detail (from your description here, I’d wondered if you had been connecting lights to your Pi).
Sounds like your Pi is picking up electrical interference from the mains-spark caused when you toggle your light-switch. You might be able to eliminate the interference by using proper pull-up resistors in your circuit, instead of relying on the Pi’s internal pull-up resistors.
Yep – the Python section is the best place for RPi.GPIO type stuff. There’s a few people on there who know an awful lot about this sort of stuff. :)
It’s where I go for help.
Thanks a lot for helping me, anyway i’m not sure to be able to use pull up resistor cause raspberry is far from the buttons and they are connected only to gnd and gpio.. There is no software way to eliminate this interference?
I did a test using this code
import RPi.GPIO as GPIO
GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_UP)
And i got something like ten 1 and then ten 0 then ten 1 and again ten 0 and so on…
Having long wires is a great way to pick up interference :-/ Unless your wires are shielded, they effectively act like aerials, so the longer the wire the more interference you’ll pick up. And this is just about where my knowledge on this subject runs out…
No, there’s no way to “fix this in software”.
Can’t the resistors go at the Pi end of the wires?
Well if resistor can go at the end of the wires i can do it.. This afternoon i will try.. On the raspberry pi forum a user told me to use a capacitor in parallel with the buttons..
I know that helps with debounce. If it helps with floating to, that’s great.
I think a capacitor can sometimes be used to filter out “spikes” of interference (IIRC it’s the hardware equivalent of what software de-bouncing does), but this is all beyond my depth – I’m a software guy not a hardware guy ;-) Good luck!
I’ve seen with the digital multimeter that when the button is not pressed there is a very big resistance like 200Kohm maybe if i add a resistance in series i could delete any interference or not? (just like AndrewS i like software and i don’t know so much about hardware)
Perhaps i did it!! =D It seems to work correctly.. I’ve connected everything as following:
Ops ahah, here is the link https://www.circuitlab.com/circuit/b66q2m/domopi-buttons/
That looks OK to me, but I’m not an expert. Well done if it works :)
Eeek, that shows 5V connected to the Pi’s GPIO pins! You’re only supposed to connect 3.3V to the GPIO pins, otherwise you risk damaging them.
I think the 10k + 1k resistors limit the current enough to make it a non-fatal condition.
I seem to remember a post by Mah Jongh on the Pi forums where he said something like ‘you don’t need level converters as long as you have a >1k resistor in series with the port’. That’s not a quote though – it’s what I remember from the thread.
As AndrewS points out, THE DIAGRAM SHOWS 5V CONNECTED TO THE Pi’s GPIO INPUT (PORT 24) – WHEN THE BUTTON IS PUSHED. alex says “I think the 10k + 1k resistors limit the current enough” … BUT THERE ARE NO RESISTORS IN THE DIAGRAM. CORRECTION NEEDED.
Andrew is talking about another diagram linked to in the comments by Daniele – one that’s on circuitlab.
My diagram above is connected to 3V3. If you take a close look you will be able to verify that for yourself.
Hi, another question, i set up a switch (not a button), how can i read the state of that switch? I tried with this code but it don’t work, thanks for any help =)
if GPIO.input(21) == 1:
state = "1"
print = "Off"
state = "0"
That should work fine (assuming your switch is connected to GPIO21 if you’re using BCM), but only once unless you put it in a loop.
Also, you don’t need the “== 1”, simply
will return 1 or 0 (HIGH or LOW and True or False).
Thanks for answering =) well I’m using BOARD maybe that’s why it don’t work?
Well are you using pin 21 or GPIO 21? Have you read the RPi.GPIO basics series on here this week? It’s all covered.
Pin 21 is GPIO9
GPIO21 is pin 13 if you have a Rev 1 Pi. If you have a Rev 2 it doesn’t exist.
I’m using pin 21 on rev 1 so gpio 9
There’s no reason that a switch shouldn’t work identically to the way a button works – electrically they’re either on (closed) or off (open). How have you got the switch wired up, what pull-up / pull-down settings (or resistors) are you using, etc. ?
I’ve connected the switch one wire to the gnd and the other to the pin 23 and this is the script i use.. even if i have the switch on sometimes it print on and sometimes off..
#import RPi.GPIO library as GPIO
import RPi.GPIO as GPIO
import MySQLdb as mdb
#Set GPIO numbering scheme to pinnumber
#setup pin 23 as an input
print "Light ON"
state = "1"
print "Light OFF"
state = "0"
con = mdb.connect('localhost', 'root', 'rasp', 'domopi');
cur = con.cursor()
cur.execute("UPDATE settings SET light=" + (state) + " WHERE id=1")
Ok i got it working just adding pull_up_down=GPIO.PUD_UP
Thanks anyway =D
Ahhh, so you just needed to
RTFMRAFG – Read Alex’s Fabulous Guides :-)
Before my questions, can I just thank you for all the work you have done on your site. Without it I would be far more confused than I already am, and would never have got to grips with the Gertboard.
Now the questions:
1. Why have you used the function, e.g.,
GPIO.add_event_detect(24, GPIO.RISING, callback=my_callback)
when the manual on the raspberry-gpio-python/wiki page provides the function
which appears to do the same job? Or does it?
2. How does one know when a “word” such as “channel” in a function has to be left as it is, as in
and when it has to be substituted with and actual value, such as 24 in
GPIO.add_event_detect(24, …) above?
1. GPIO.add_event_callback(….) is used if you want to run more than one callback function from a single interrupt event.
2. Simple answer, you rely on good documentation conventions and up to date documentation which is not always fool-proof. Sometimes you just have to “poke it and see” what happens. :)
I wasted some time yesterday trying to get tweepy working, only to realise that the official documentation was WAY out of date and what I was trying to do would never work again (they’ve changed the way twitter authentication works).
Aha! Many thanks. H.
is a function definition, i.e. channel is a local variable inside the function which contains the value passed to the function.
is you calling a function, i.e. inside the add_event_detect function (which is inside the GPIO module) there’ll be some local variable inside it set to the value 24.
would print out “Hello Harry” and then “Hello Alex”.
You’ll naturally get the hang of these things once you’ve been programming for a while ;-)
Alex could you fix the code formatting for me again please? ;)
certainly not ;P
Aha! again. I’m afraid I come from the days of Algol and punched cards, so am v rusty, but I’m sure it will all gradually make sense. Thanks for taking the time to write the example code _ I’ll make a note of it.
Can you increment a variable within the interrupt ‘call back’ DEF function ?
I am trying to increment a variable every time an interrupt occurs. eg X=X+1 to count the number of interrupts that has happened. But when I include x=x+1 or similar into the DEF routine that is called by the interrupt, I keep getting ‘UnboundLocalError : local variable ‘x’ referenced before assignment. It appears not to like having ‘x’ declared twice for some reason. Is there a way around this so I can keep count of interrupts that has occurred ?
Please can anyone help, its so frustrating trying so many ways but keep failing.
You need to put
In your function before you refer to x
Otherwise its scope is only within the function
Globals are bad and should generally be avoided. There are a couple different ways of persisting the value of a variable between calls, based on the different paradigms that Python supports:
define a class that stores the value as an attribute.
define a higher-order function (HOF) with the value stored in a local variable. The HOF also defines the counter function. Before Python 3.2, an inner function couldn’t modify a variable from an outer function (assigning to a variable in the inner function creates a new variable of that name local to the inner function), so a mutable data structure (such as a dict) must be used to store the value.
As of Python 3.1, an inner function can write to an outer variable by using the nonlocal statement:
Note that at a certain level, the two approaches are the same, as the 2nd is a simplified form of how you’d implement OOP in FP (define a higher order function, a “constructor”, that creates attributes as local variables & private methods as inner functions and returns a function that processes messages as the instance).
Thanks for this. You are of course right, and thank you for showing different ways around it.
But I think you can see, from the point of view of someone not very advanced with Python, the most important thing is “making it do what you want”. If it does what you want by making a variable global, using one line of code, it helps you to get the job done in a much simpler way.
I realise there is a danger that it becomes a habit. But there is also a danger that you can make things too hard for people when they’re at a low level of experience.
If you have to learn about classes, higher order functions and inner functions, in order to “make it do what I want”, you’re less likely to see it through.
It’s really good to have someone explain “how to make it do what you want in a more robust and scalable way” though, so thank you, and please keep adding your input. It’s most welcome. :)
AndrewS has emailed me to say he’s been unable to post this…
(If he manages to post it later on, I’ll delete this version)
Ordinarily I’d agree with you, but even the page you linked to says
“In a very small or one-off programs …. using globals can be the
simplest thing that works.” and I believe Alex tries to keep his stuff
simple and aimed at beginners, not programming experts ;-)
Also, in this specific scenario you can’t even pass an object to the
callback function because RPi.GPIO provides no mechanism for doing so
so you have to use a global variable. You might be able
to use a lambda function (I haven’t tried experimenting) but that’s
definitely beyond the scope of Alex’s beginner-focused blog.
The “generally” in my post was my out for this. My thinking when writing was:
1. If not exposed early to good practice, bad practices quickly become personal habit.
2. It’s not very helpful just to say “that’s bad; don’t do that”, so some sort of code sample is in order.
3. The code samples required for the better techniques aren’t very long. Even if all aspects of the techniques aren’t understood, they can still be used. The techniques then (hopefully) become a bridge to learning.
4. I briefly considered adding “but globals can be OK in very short programs that you’re going to discard”, but once someone has that sort of permission, they tend to put off learning the appropriate technique.
Also, what starts as one-off code all too often shows up in long-term projects, especially when posted as sample code on a such a useful website.
In the end, this is a debate about pedagogy, which (being more philosophy than science) doesn’t have hard answers. Either perspective may be the more effective for a given student.
Python has a very nice feature that enables GPIO functions to take objects as callbacks. The GPIO functions take callables, and callables can be objects or functions (starting with Python 3, there is no longer any difference, as functions are objects). All it takes to make a callable object is for it to have a
I disagree that lambdas (or higher-order functions) aren’t suitable for beginners. They’re taught day-one to underclassmen in any class on functional programming. Anonymous functions are no more surprising to a novice than anything else. They may seem that way to those that come across them later, but that’s only because it’s been so long that they’ve been surprised about some programming feature that they may not realize it’s due to the novelty of it, rather than any inherent difficulty. Essentially, that surprise is an emotional regression (but a very good one, because you get to experience the wonder and joy of discovery) to the novice state. The only real difficulty is when someone is so used to one programming paradigm that it’s hard to think within another. Because of the wide emphasis on imperative programming and the late introduction of FP into most programmers experience, FP gained an undeserved reputation of difficulty.
You could sum up (which is certainly called for, given how long-winded this post is becoming (including this comment (ack))) my beliefs on topic suitability with “the only concepts unsuitable for a novice are those that require learning other concepts first”.
A comment from the ignorant end of the Python user spectrum.
In replying to a question of mine in Part 2 of this series, Alex suggested I used a global variable to access an interrupt event (accumulating output from a rain-gauge), and I understood it, and I did it, and it worked first go.
Outis has been very positive in his criticism of “global” by giving 3 ways in which “global” might be avoided. I have tried very hard, but I can’t see how these could be incorporated in GPIO callbacks. He mentions that these methods are introduced early on in Python classes.
I think that there is a different learning process if you are (a) a bright young student in a well-taught well-structured class (does Outis teach these?), or (b) an old bloke like me picking up bits as he goes along, often from this excellent blog.
I was going to suggest that, if Outis has the time and patience, I would love to see how he would incorporate one of his more “moral” solutions with the limitations of GPIO interrupts. However i notice below that AndrewS has already done this.
A footnote: I am always pleasantly surprised and encouraged by the generosity of the contributors to this blog. Thank you all.
Yes this has turned into a really good discussion. Thanks all for taking part :)
Um, I’m afraid I hadn’t! My code below was still using a global. Now that Outis has taught me about __call__ (even old programmers still learn new tricks!) I’d use his Counter class example from above, you’d just need to pass c as the RPi,GPIO callback ‘function’, and then the current value of c can be read elsewhere in the program with c.x
Although from a pedagogical point of view, single-letter variables are probably a worse habit than global variables ;-)
AndrewS: Oh dear, I hadn’t noticed the “global” in your example.
I tried to follow your suggestion to use Outis’ Counter example, by using a button to connect GPIO4 to 3V3, and running the short program attached at the foot of this comment.
The relevant commands are:
count = Counter() # I renamed Outis’ variable “c” as “counter”, following your final comment
GPIO.add_event_detect(4, GPIO.RISING, callback=count, bouncetime=300)
but, whenever I press my button I get the following rude message:
TypeError: __call__() takes exactly 1 argument (2 given)
Have I mis-construed your suggestion? (This is very likely.)
[I have checked the v simple circuit, and it works in a program that uses a function with a (deprecated) global variable.]
The purpose of this trial:
to detect an event (in this case connecting GPIO 4 to 3V3 via a button)
using GPIO interrupts but without declaring a global variable
import RPi.GPIO as GPIO
# extend ‘object’ for a new-style class in Python 2.X as suggested by Outis
def __init__(self, start=0):
self.x = start
self.x += 1
print “Count inside object “,self.x
GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(4, GPIO.RISING, callback=count, bouncetime=300)
for i in range(30):
print “At “,i, ” secs the count outside object Counter was”, count.x
print “The End”
There’s not too much to it. For the OOP approach, the plain callback basically becomes the
__call__method, and for FP it becomes the inner function returned by the
make_*constructor. You can think of it in terms of code rewriting, where:
def __init__(self, default values...):
# initialize instance variables
# note that the callback arguments are added to __call__
def __call__(self, args...):
In each case, the body requires the minor rewrite (signified by body’ rather than body) of renaming the variables that keep their values between calls.
* For OOP, simply prefix the variables with “self.” (and be sure to initialize them in
__init__). For example, foo becomes self.foo
* For FP under Python < 3, replace the variables with a lookup in the dict you created that holds locals. For example, foo becomes locals[‘foo’]
* For FP under Python >= 3, add the variables to a “nonlocal” statement (the rest of the callback body remains unchanged).
Compare the samples in my previous post with what I could infer to be in Derek’s function:
x += 1
If that’s not enough for you to write a solution, let me know and I’ll post a more complete example.
<clarification mode=”pedant”>Actually, I was referring to anonymous functions and HOF being introduced to underclassmen in classes covering FP (though OOP similarly is taught early on). When Python makes an appearance in an class, it’s typically a teaching tool, rather than the subject of a class.</clarification>
Yeah, it seems like no matter how much time I spend editing, there’s always at least one mistake. Keeps the gods from punishing me, the Greeks might have said.
Ahhh, the problem here is that you’ve explicitly defined Counter.__call__ to only take the ‘self’ argument, but it looks like RPi.GPIO is trying to pass an additional ‘channel’ argument to the callback function. So you need to add an extra parameter to your __call__ function’s definition:
def __call__(self, channel):
Alternatively you could say your function is allowed to ‘scoop up’ any number of arguments by doing:
def __call__(self, *args, **kwargs):
– as you don’t have any need for these ‘extra arguments’ inside your function, it’s safe to just ignore their values. See this for more info.
Thanks to Outis for his examples and for getting me to look at OOP – I have now been reading up on it in a real (paper) book. Phew! Old dogs, new tricks.
Thanks to AndrewS for his suggestion about including the extra channel parameter – it now works!
Note to Alex: As the guys have helped me get this to work, I thought you might like to have the following functioning code on your blog for other confused beginners like me. Please edit or remove as you see fit – I’m not too confident about the formatting being maintained. HarryT.
The purpose of this demo program (in Python 2.7):
To detect an event using GPIO interrupts without declaring a global variable.
Based on suggestions by Outis and AndrewS on the RasPiTV forum.
The simple circuit is:
3V3 --- Button --- LED --- 68ohm resistor --- GPIO4.
Every press of the button increases the value of count by 1.
This program actually works!
import RPi.GPIO as GPIO
# extend 'object' for a new-style class in Python 2.X as suggested by Outis
def __init__(self, start=0):
self.x = start
def __call__(self, channel):
self.x += 1
print "Value of accumulator inside object is:", self.x, "\n"
GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(4, GPIO.RISING, callback=count, bouncetime=300)
print "Press a button, starting now. You have 30 secs.\n\n"
for i in range(30):
print "At ",i, " secs the value of count outside object Counter was:", count.x
print "The End"
Another stylistic tweak you could make (since we’re discussing ‘best practices’) would be to use string formatting, rather than direct string concatenation, to e.g. replace:
"At ",i, " secs the value of count outside object Counter was:", count.x
"At %d secs the value of count outside object Counter was: %d" % (i, count.x)
And the final call to GPIO.cleanup() is missing its brackets (I suspect that may have been a copy’n’paste typo?).
And instead of accessing the count.x variable directly from outside the Counter class, you could consider making it a read-only property (although again it’s not really worth it for such a small program).
It’s another stylistic thing, but personally when I use the same value more than once in a program (e.g. the 30 in your code above) I like to replace it with a variable instead (so that if I ever want to change its value later, I only need to change it in one place).
Always plenty of new tricks to learn! ;-)
Thank you Andrew for spotting my missing brackets – you correctly identified a botched cut & paste job.
I hadn’t thought of string formatting, though I’ve been a bit wary of it as it seems quite different in Python 3. (My brain is still in Algol-land, I suppose.)
The little for loop was just a quick and dirty attempt to give the proof-of-principle program something to do. I really should input both the number and length of printing intervals using raw_input, as it runs too fast at the moment.
A steep learning curve!
Once again, thank you for taking the time and trouble to help me out.
I have been following these comments with interest after my initial question which needed the ‘global’ command. However, I notice in the last comment the print statement contained ‘%d’. I have seen this before with other similar notations, but I do not understand what %d does or what is it for ?. I have read all my books on Raspberrypi & Python but can not find any reference to this or similar notations. Can anyone please explain why %d etc is used and point me in the right direction on where I can read up on this subject.
ps I will still stick to ‘global’ as it is easier for me to understand.
Writing as one beginner to another, I sending this address of a page dealing with strings, on a python tutorial site that I have found more accessible than the official documentation and tutorials:
(sorry, I don’t know how to paste this as a link.)
Harry, many thanks, this appears to be an excellent site for answers. Now set as a ‘favorite’ on my pc, as it will no doubt be referenced many times .
I hope the link’s useful, Derek.
I also actually did find the “official” site useful (see link below): not the formal definitions (which are doubtless rigorous but opaque to me), but by working through the examples in section 184.108.40.206, and saving the resultant files containing my own efforts for future reference.
Here is the link:
It took me a whole evening fiddling about, trying to understand the Python logic (it’s a bit like learning to read from right to left), but it was either that or do a crossword.
PS Apologies to Alex if all this discussion is going a bit off-piste.
Thank you so much, it now works. I can now get some sleep !!!.
However I would like to know what does ‘global’ y actually do. and also in the interrupt detect statement you use the command ‘callback=’ this points to a DEF function, but is there any other commands/key words that can be used instead of callback as to do other things on an interrupt ?.
Derek you need to look up “scope of a variable in a function in python” or something like that.
As to your second question, not as far as I know. But you can do pretty much anything you want in a function (even call other functions, or, as you now know, globally set the value of variables defined in other functions) so it’s not really a handicap.
Relevant web-links for further reading:
Can this method be used for GUI buttons using the tkinter library? I have a motor and a driver and can make the motor turn one way with pin 11 and turn the other way with pin 12 what I want to do is make a simple GUI with 2 buttons one for forward one for back.
When I click the forward button it works but I don’t know how to make it stop or use the back button to change direction, here is a sample of the code.
#Create Elements within the frame
self.header = Label(self, text = “NB1 Robot Control”)
self.forward = Button(self, text = “Forward”)
self.forward[“command”] = self.forward_but
self.backward = Button(self, text =”Backward”)
self.backward[“command”] = self.backward_but
GPIO.output (11, False)
print “Move Forward”
GPIO.output (12, False)
print “Move Backwards”
Any help would be very much appreciated
I guess you probably want a global ‘direction’ variable, and then in your GUI callback you could do something like
and then similar code for your forward_but callback function (and for stop_but if you decide to add a “Stop” button too)
Hopefully Alex will apply his magic code-formatting tags so that you can see the indentation correctly ;-)
Thanks AndrewS, This looks perfect for what I need, I had before seeing this got the basic functions working with just turning the pins on and then created a 3rd button saying “STOP” which set all pins back to “False” which does the trick but is a bit clunky!
I will try and get your script working and see if it runs smoother, next step is I need to integrate it with webiopi :s
Thanks again for the help :)
No problem. Sometimes solving a programming problem just involves taking a step back and doing a bit of logical thinking :)
I love rpi gpio, very easy to use. But there is some confusion on what it does for event handling. To be clear, it does NOT use real interrupts. It does check the GPIO pin for a state change and then launch an event handler. Just know that this does not involve the cpu’s interrupt capabilities.
See my comment here:
[…] my previous tutorials on threaded callbacks we used edge detection of RISING and FALLING edges. Somewhere along the line, Ben introduced an […]
Use the ‘finally’ clause for all clean-up.
print “Waiting for falling edge on port 23”
print “Falling edge detected. Here ends the second lesson.”
print “Exit by CTRL-C”
GPIO.cleanup() # clean up GPIO on for ALL exit
Yeah, Alex covers that in a later tutorial :-)
I tried the threaded callback for interrupt like this after following your tutorial
It works only once. what is going wrong?
Thanks for this tutorial it is exactly what I need to do, but I have one question.
My program is uses gammu to recieve SMS commands and send SMS replies in the main thread. Now I wanted to use GPIO interrupt and a reed switch to detect if a door is opened and use the callback function to send an SMS notification. This caused errors, which I tried to catch but still doesn’t seem to be working. I think the problem may be because both threads are trying to access the modem at the same time. maybe?
I thought a solution could be to use the callback function to change a global variable, and then in the main thread you could check that, send the SMS and then reset the variable. I still haven’t implemented that yet, but my question is this:
I know you are supposed to avoid global variables, but is this a good reason for using one? Is there any other way for the callback function to return or pass a value back to the main thread?
Yeah, that’s probably a good place to use a global variable, treating it as a boolean flag. (There’s lots more discussion about global variables and their alternatives if you read all the comments on this page)
You’ll obviously need to be careful that if you leave the door open, you don’t end up sending constant SMS notifications! And you’ll probably need to experiment to find a suitable bouncetime value for your reed switch setup, so that opening or closing the door doesn’t trigger multiple ‘false’ interrupts.
Note that a simple boolean flag can lead to subtle timing bugs: one thread may test the flag and be interrupted by the other before the first can toggle the flag, defeating the whole purpose of the flag. Also, the flag itself is only part of the solution, as you’ll need a way to block a thread until the flag indicates processing can proceed. Thread synchronization needs special tools, such as mutexes, semaphores and message queues.
A mutex (short for “mutual exclusion”) limits access to a resource to just one thread by a very simple mechanism: before entering a critical section of code, the thread attempts to acquire the mutex. The acquisition blocks until the mutex is available, at which point the thread becomes the mutex owner and `acquire()` returns. When leaving the critical section, the thread releases the mutex. While Python 2 has mutexes, they’ve been deprecated in favor of locks. The main difference between a mutex and a lock is that any thread can unlock a lock (i.e. locks don’t have owners), but only the thread that acquired a mutex can release it. Also, Python’s mutex implementation accepts a function to execute when the lock is acquired.
A semaphore is like a mutex but allows multiple threads to acquire it, up to some limit (the exact count is set when the semaphore is created). A lock is a semaphore that allows 1 thread (a.k.a. a binary semaphore). A mutex is basically a binary semaphore with an owner. In this case, you don’t need a semaphore.
Message queues are your basic FIFO queue with synchronization built-in, so multiple threads can access them without problems. They allow producer & consumer threads: some threads create jobs by adding a message to the queue, some threads consume jobs by taking messages from the queue. In this case, since the main thread is a producer, another thread would be added as a consumer.
Looking at gammu’s docs, it already implements a message queueing model (the gammu daemon is the consumer). If simultaneous access is the cause of the errors, it’s the accessing of the Python objects, rather than the daemon or modem. GammuWorker is a Python-level producer (if you look under the hood, you’ll see that GammuWorker uses message queues and threads). If you’re already using GammuWorker, then something else is causing the errors.
As always, sample code is intended as an illustration, rather than a ready-made solution.
Aha! I hadn’t read all the comments since the thread is so long. Thanks Outis for giving the good examples of OOP and FP. I plan on implementing one in the future.
So far I have just implemented it as a global boolean called gateChanged. The interrupt is set to detect both rising and falling edges. The callback sets gateChanged to True.
Then the main loop checks if gateChanged is True and sends you an appropriate SMS notification whether the gate has been opened or closed. Then gateChanged is reset to False. This seems to work fine without any timing errors but needs more testing.
Thanks Outis for the info about mutexes and semaphores too, I may have to implement something like that in the future. Also thanks for the details about gammu.
I haven’t used GammuWorker yet. So it is just used for queuing commands for the modem? I looked at it but it seems really complicated for the purpose of my program.
All I do is very basic stuff based on the gammu examples and documentation. I loop through all the sms check if they are in my dictionary of commands, do some things with GPIO, send an appropriate reply, then delete the sms.
What is the benefit of using GammuWorker? Thanks.
Thank you for the tutorial, I’m developing a Quizz game with switches for teams using events to get the one who press first.
Do you know a way for doing a GUI for it?
I cannot get a web interface reacting to the physical buttons press (is this possible?)
I’ve never used either of them myself, but you might want to look at http://webiopi.trouch.com/ for a web-interface, or https://pygame-zero.readthedocs.io/en/latest/ for a simple python GUI.
I am using mentioned interrupt with pull down , so with rising edge. It works fine, only when I swith on my TL-Lamp or printer, this interrupt reacts (raspberry pi 3) several times, but the pin level change is not visible on a 60 Mhz scope. I solved the problem by checking the pin 5 msec later. bounce does not work. But the interrupt is still happening. This problem was not on the old raspberry pi B (with RPIO instead of RPI.GPIO). I would like to completelely the erratic interrupt….any idea ?
thanks in advance