I am trying to create a program that detects the state of three different buttons, connected to GPIO pins on a Raspberry Pi, and once all three have been HIGH once, an action is taken. Right now I have all the buttons working individually through callback functions, but the if
statement inside the 'main' function does not seem to be running.
This is my first time using Python so please let me know if you see any other logical errors in the structure of my code. Still trying to get a hang of it, especially the GPIO library functions. Thanks in advance, I've posted my code below.
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
butOne = False
butTwo = False
butThree = False
# Setup button inputs
GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(21, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(19, GPIO.RISING)
GPIO.add_event_detect(20, GPIO.RISING)
GPIO.add_event_detect(21, GPIO.RISING)
def butOne_callback(channel1):
print("Button 1 /n")
butOne = True
def butTwo_callback(channel2):
print("Button 2 /n")
butTwo = True
def butThree_callback(channel3):
print("Button 3 /n")
butThree = True
def main():
GPIO.add_event_callback(19, butOne_callback)
GPIO.add_event_callback(20, butTwo_callback)
GPIO.add_event_callback(21, butThree_callback)
if (butOne == True) and (butTwo == True) and (butThree == True):
print("All Depressed")
main()
UPDATED CODE, according to Aditya Shankar's suggestions:
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(21, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(19, GPIO.RISING)
GPIO.add_event_detect(20, GPIO.RISING)
GPIO.add_event_detect(21, GPIO.RISING)
def butOne_callback(channel1):
print("Button 1 /n")
butOne = True
check_all_depressed()
def butTwo_callback(channel2):
print("Button 2 /n")
butTwo = True
check_all_depressed()
def butThree_callback(channel3):
print("Button 3 /n")
butThree = True
check_all_depressed()
def check_all_depressed():
if butOne and butTwo and butThree:
print("All Depressed")
GPIO.add_event_callback(19, butOne_callback)
GPIO.add_event_callback(20, butTwo_callback)
GPIO.add_event_callback(21, butThree_callback)
Error received when code is run and button is pressed:
Traceback (most recent call last): File "/home/pi/Downloads/GPIO_test_06.py", line 21, in butTwo_callback check_all_depressed() File "/home/pi/Downloads/GPIO_test_06.py", line 29, in check_all_depressed if butOne and butTwo and butThree: NameError: name 'butOne' is not defined
Your if
statement runs, but only once - immediately when the script is first started. By that time, the buttons haven't been pushed and thus it seems like it's not working.
One way of solving that would be to put the statement in a loop with a small delay and test for the condition in that loop. Something like:
import time
while not condition:
time.sleep(1)
Another issue is one of style. You can write your condition:
(butOne == True) and (butTwo == True) and (butThree == True)
simply as:
butOne and butTwo and butThree
since they all are boolean values to begin with. In Python, you can even write:
all([butOne, butTwo, butThree])
That's not shorter, but if you had even more conditions, it would avoid repeating and
again and again.
Finally, you've chosen to create a main function, that runs the main program. It's probably a good idea to include all the code above your function definitions in there as well. After all, it's all part of your main program and it is all meant to run only once. This way, you also avoid accidentally using global variables inside of functions, which can result in unexpected (but technically correct) behaviour.
Answer:
remove the if condition
add a function check_all_depressed()
add the function to the end of all three button callbacks, like this
def butOne_callback(channel1):
global butOne
print("Button 1 /n")
butOne = True
check_all_depressed()
check_all_depressed looks like this -
def check_all_depressed():
if butOne and butTwo and butThree:
print("All Depressed")
Explanation: So, there are callbacks and there is general program flow. basically python programs follow an order of events of occurrence, that is top to bottom, callbacks occur outside this flow.
Fundamentally, the way the GPIO package supports events is by waiting for rising and falling edges on the channels you select. This is done in a background thread, regardless of what happens in the main thread. Your if
statement runs once, immediately after the buttons are configured, and then the main thread ends.
There are two types of solutions you can implement. One is to force the main thread to wait for a change in state. The other is to respond to changes in state in the callbacks.
To force main
to wait:
import RPi.GPIO as GPIO
channels = [19, 20, 21]
def main():
GPIO.setmode(GPIO.BCM)
for chan in channels:
GPIO.setup(chan, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
for chan in channels:
GPIO.wait_for_edge(chan, GPIO.RISING)
# Now all your buttons have been pressed
This is probably the more efficient way to do it. Minimal setup and no explicit multithreading.
The alternative is to listen for the input in a separate thread. You can configure your callbacks to respond to a rising edge as you have done with add_event_callback
, but keep in mind that that function is mostly for setting up multiple callbacks. A more succinct way would be to move the calls to add_event_detect
into main
and combine them with add_event_callback
:
GPIO.add_event_detect(chan, GPIO.RISING, callback=...)
From a programmatic point of view, I'd use the fact that all the channels are treated almost the same and only define one callback. In fact all that matters in your setup is the channel number and the channel name:
import RPi.GPIO as GPIO
channels = {19: 1, 20: 2, 21: 3}
class callback:
def __init__(self):
self.pressed = dict.fromkeys(channels, False)
def __call__(self, channel):
print(f'Button {channels[channel]} pressed')
self.pressed[channel] = True
if sum(self.pressed.values()) == len(self.pressed):
# All buttons have been pressed
def main():
GPIO.setmode(GPIO.BCM)
cb = callback()
for chan in channels:
GPIO.setup(chan, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(chan, GPIO.RISING, callback=cb)
Notice that in both examples, I have avoided global state aside form the channel configurations. The second way sets the callback to an instance of a callable class to do so. The alternative is to define the callback as a nested function in main
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.