Using an IF statement in Python inside of a main() function - python

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

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.

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.

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.

Related

Need to take the whole python code into a function that can be access from another python file

import RPi.GPIO as GPIO
import paho.mqtt.client as mqtt
import time
def privacyfunc():
# Pin Definitions:
led_pin_1 = 7
led_pin_2 = 21
but_pin = 18
# blink LED 2 quickly 5 times when button pressed
def blink(channel):
x=GPIO.input(18)
print("blinked")
for i in range(1):
GPIO.output(led_pin_2, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(led_pin_2, GPIO.LOW)
time.sleep(0.5)
mqttBroker="mqtt.fluux.io"#connect mqtt broker
client=mqtt.Client("privacybtn") #create a client and give a name
client.connect_async(mqttBroker)#from the client -connect broker
while True:
client.publish("privacy", x)#publish this random number to the topic called temparature
print("Just published"+str(x)+"to Topc to privacy")#just print random no to topic temparature
break
def main():
# Pin Setup:
GPIO.setmode(GPIO.BOARD) # BOARD pin-numbering scheme
GPIO.setup([led_pin_1, led_pin_2], GPIO.OUT) # LED pins set as output
GPIO.setup(but_pin, GPIO.IN) # button pin set as input
# Initial state for LEDs:
GPIO.output(led_pin_1, GPIO.LOW)
GPIO.output(led_pin_2, GPIO.LOW)
GPIO.add_event_detect(but_pin, GPIO.FALLING, callback=blink, bouncetime=10)
print("Starting demo now! Press CTRL+C to exit")
try:
while True:
x=GPIO.input(18)
print(x)
# blink LED 1 slowly
GPIO.output(led_pin_1, GPIO.HIGH)
time.sleep(2)
finally:
GPIO.cleanup() # cleanup all GPIOs
if __name__ == '__main__':
main()
need to take this whole code into a function that can be accessed from another python file.plz, help me with this coding part.
In the new python file call import module where module is the name of the file. Like led_blink.py would be import led_blink
Then you can call the methods like so:
led_blink.blink(channel)
This is assuming the files are in the same folder.
Easiest way that I do this is to take a .py file and put it in the same directory you created the python script you are working with. From there you can do the following code from The_Python_File import *
Additionally note that the "The_Python_File" is the name of the .py file that contains the function you are trying to call, but you do not include .py when importing the file.

correct way to handle GPIO interrupts when using time.sleep()

I have the following scenario. With a raspberry pi I have a main function which runs in a while loop and alternates tasks between calls to sleep. I would like a button press to interrupt the main loop and do another task for a certain amount of time before returning to the main loop. In reality I am displaying output on an LCD screen but I coded up this simpler example to illustrate the problem with my logic. I think things are getting crossed up because both functions seem to be active at the same time? I don't know the correct way to handle this scenario. Can someone suggest how to do this properly?
from time import sleep
import RPi.GPIO as GPIO
BUTTON_PIN = 2 # GPIO pin for mode button
def main():
print("mode1 part A")
sleep(4)
print("mode1 part B")
sleep(4)
def run_mode_two():
# I would like this function to full execute before retuning to main
print("mode2")
sleep(8)
# function to be called by interrupt
def button_released_callback(channel):
run_mode_two()
# intialize gpio for button
GPIO.setmode(GPIO.BCM)
GPIO.setup(
BUTTON_PIN,
GPIO.IN,
pull_up_down = GPIO.PUD_UP)
# interrupt to listen for button push
GPIO.add_event_detect(
BUTTON_PIN,
GPIO.RISING,
callback = button_released_callback,
bouncetime = 300)
while True:
main()
The following pseudo code for your reference. Only focus on the flow, please adapt it to your own needs.
mode = 1
def button_callback():
mode = 2
start_timer(5)
def timer_callback():
mode = 1
def loop():
if mode == 1:
# run code in mode 1
else:
# run code in mode 2
def main():
# init code here
# ...
while True:
loop()

Python: threaded callback not working with daemon mode?

I'm new to Python.
I've a threaded callback code working fine on my raspi.
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
import time
from daemon import runner
GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # float switch goes down (closed to open) => low water level
GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_UP) # float switch goes up (opened to close) => high water level
def callback_lowlevel(channel):
if GPIO.input(channel):
print "Low water level in the sump detected"
else:
print "Water level in the sump returned to normal"
def callback_highlevel(channel):
if GPIO.input(channel):
print "High water level in the sump detected"
else:
print "Water level in the sump returned to normal"
GPIO.add_event_detect(23, GPIO.BOTH, callback=callback_lowlevel, bouncetime=1000)
GPIO.add_event_detect(24, GPIO.BOTH, callback=callback_highlevel, bouncetime=1000)
If I start an infinite loop like this:
try:
print "Waiting for events"
while True:
time.sleep(1)
except KeyboardInterrupt:
GPIO.cleanup() # clean up GPIO on CTRL+C exit
GPIO.cleanup() # clean up GPIO on normal exit
It works.
But if I "daemonize" it with the daemon library, my threaded callbacks are just not working anymore.
class App(): # Daemon content, not doing much, sleeping mostly, to lower CPU footprint
def __init__(self):
self.stdin_path = '/dev/null'
self.stdout_path = '/dev/stdout'
self.stderr_path = '/dev/stdout'
self.pidfile_path = '/var/run/aquamonitor.pid'
self.pidfile_timeout = 5
def run(self):
Logger("Starting monitoring")
while True:
time.sleep(1) # Sleep most of time to be not too CPU intensive
app = App() # Init the App
daemon_runner = runner.DaemonRunner(app) # Run as a daemon
daemon_runner.do_action() # Just do it
What am I doing wrong? Does the fact that it's a daemon changes the way I'm supposed to write my threaded callbacks?
I had the same issue and I figured I was registering the callback to the wrong thread. So, long story short, be sure that GPIO.add_event_detect is called from within the run method of your App class, just before the loop.

Python "shutdown / reboot raspberry pi" script, using a single button

I got a piece of code in Python from here:
http://www.raspberry-pi-geek.com/Archive/2013/01/Adding-an-On-Off-switch-to-your-Raspberry-Pi
And I wanted to improve on it.
Since this is the first time I work with Python, I got stuck on understanding what actually happens.
Here is the code:
# Import the modules to send commands to the system and access GPIO pins
from subprocess import call
import RPi.GPIO as gpio
from time import sleep
gpio.setmode(gpio.BCM) # Set pin numbering to board numbering
gpio.setup(22, gpio.IN) # Set up pin 22 as an input
rebootBool = 0
# Define a function to keep script running
def main(pin):
while True:
#gpio.remove_event_detect(pin)
gpio.add_event_detect(22, gpio.RISING, callback=confirmation, bouncetime=200) # Set up an interrupt to look for button presses
sleep(5000000)
def confirmation(pin):
gpio.remove_event_detect(pin)
gpio.add_event_detect(22, gpio.RISING, callback=shutdown, bouncetime=200)
sleep(3) # if button has been pressed again within 3 seconds, shut down will happen
main(22)
def reboot(pin):
rebootBool = 1
call('reboot', shell=False)
exit(0)
# Define a function to run when an interrupt is called
def shutdown(pin):
gpio.remove_event_detect(pin)
gpio.add_event_detect(22, gpio.RISING, callback=reboot, bouncetime=200)
sleep(3) # if the button has been pressed for a third time, within 3 seconds, Pi will reboot
if rebootBool == 0: # Just to make sure a halt is not called after the 3 seconds have passed, if reboot is called
call('halt', shell=False)
exit(0)
main(22) # Run the loop function to keep script running
What I want to do is this:
If button is pressed once, a confirmation function is executed, in which it resets the button to call the shutdown function, if it's pressed within 3 seconds.
Else, resume with the main loop
In the shutdown function, if it's pressed again (within 3 seconds), it resets to call the reboot function.
Else go on, and shut down
What happens is this:
If I press the button twice or three times, it tells me that gpio.add_event_detect is already defined, when it tries to define it in main().
So it doesn't change it, and if I press it one more time, it calls the shutdown function.
What I don't understand is:
Why does it want to define the gpio event in main, when the actual function is either reboot or shutdown (and it should call either reboot or shutdown)?
Because a callback function is running in a separate thread. This means, for example, when you call any callback function in the main loop it still works(main loop), and thus it happens when you call a function gpio.add_event_detect from a callback function(thread #1) and from the main loop(thread #2) at the same time. It's typical race condition.
You can check it by running this code:
#!/usr/bin/python
# Import the modules to send commands to the system and access GPIO pins
from subprocess import call
import RPi.GPIO as gpio
from time import sleep
gpio.setmode(gpio.BCM) # Set pin numbering to board numbering
gpio.setup(22, gpio.IN, pull_up_down=gpio.PUD_DOWN) # Set up pin 22 as an input
rebootBool = 0
# Define a function to keep script running
def main(pin):
while True:
print "main loop"
gpio.remove_event_detect(22)
gpio.add_event_detect(22, gpio.RISING, callback=confirmation, bouncetime=200) # Set up an interrupt to look for button presses
sleep(1)
#raw_input()
def confirmation(pin):
print "confirmation1"
sleep(5)
print "confirmation2"
gpio.remove_event_detect(22)
gpio.add_event_detect(22, gpio.RISING, callback=shutdown, bouncetime=200)
sleep(5) # if button has been pressed again within 3 seconds, shut down will happen
gpio.remove_event_detect(22)
main(22)
def reboot(pin):
rebootBool = 1
print "reboot"
#call('reboot', shell=False)
exit(0)
# Define a function to run when an interrupt is called
def shutdown(pin):
print "shutdown"
gpio.remove_event_detect(pin)
gpio.add_event_detect(22, gpio.RISING, callback=reboot, bouncetime=200)
sleep(3) # if the button has been pressed for a third time, within 3 seconds, Pi will reboot
if rebootBool == 0: # Just to make sure a halt is not called after the 3 seconds have passed, if reboot is called
#call('halt', shell=False)
print "halt"
exit(0)
main(22) # Run the loop function to keep script running
So, after Anton Glukhov explained me what to look out for, I found out how to solve every issue, and now it's working flawlessly.
This is the link that better explains what I did:
http://sourceforge.net/p/raspberry-gpio-python/wiki/Inputs/
Unfortunately, I wasn't able to do it any other way than use a while loop in the main function.
But since there is nothing critical going on, a sleep of 1 second has been added to each loop, ensuring minimal CPU usage.
I am sharing the final code here, in hopes that it might help others:
# Import the modules to send commands to the system and access GPIO pins
from subprocess import call
import RPi.GPIO as gpio
from time import sleep
from os import system
gpio.setmode(gpio.BCM) # Set pin numbering to BCM numbering
gpio.setup(22, gpio.IN) # Set up pin 22 as an input
def confirmation(pin):
#print "Confirmation Function on pin %d" % pin
system('echo Press again to confirm Shut down menu! | wall -n') # let all logged in users know
gpio.remove_event_detect(pin)
gpio.add_event_detect(pin, gpio.RISING, bouncetime=200)
sleep(2) # if button has been pressed again within 2 seconds, shut down function is called
if gpio.event_detected(pin):
shutdown(pin)
else:
#print "canceled"
system('echo Shut down canceled! | wall -n') # let all logged in users know
main()
def resetPin(pin):
#print "Pin %d has been reset" % pin
gpio.remove_event_detect(pin)
gpio.add_event_detect(pin, gpio.RISING, bouncetime=200)
def reboot(pin):
#print "Reboot Function on pin %d" % pin
call('reboot', shell=False)
exit(0)
# Define a function to run when an interrupt is called
def shutdown(pin):
#print "ShutDown function on pin %d" % pin
system('echo Press again if you want to reboot instead of shut down! | wall -n') # let all logged in users know
gpio.remove_event_detect(pin)
gpio.add_event_detect(pin, gpio.RISING, bouncetime=200)
sleep(2) # if the button has been pressed for a third time, within 2 seconds, Pi will reboot
if gpio.event_detected(pin):
reboot(pin)
else:
call('halt', shell=False)
exit(0)
# Define a function to keep script running
def main():
#print "Main Function"
resetPin(22)
while True:
sleep(1)
if gpio.event_detected(22):
confirmation(22)
main() # Run the main function to keep script running

Removing atexit tracebakc - atextit loops when it shouldn't

How can I remove the trackback from the command output, and how can I stop this looping in the atexit function?
!/usr/bin/python
import time, atexit, sys
import RPi.GPIO as GPIO
#16 = Motor 1 - Forward
#18 = Motor 1 - Back
#11 = Motor 2 - Forward
#13 = Motor 2 - Back
#6 = Ground
GPIO.setmode(GPIO.BOARD)
GPIO.setup(16, GPIO.OUT)
GPIO.setup(18, GPIO.OUT)
GPIO.setup(11, GPIO.OUT)
GPIO.setup(13, GPIO.OUT)
def exit_handler():
GPIO.cleanup()
print 'STOPPED'
while True:
GPIO.output(11, True)
GPIO.output(16, True)
print "Forward"
time.sleep(5)
atexit.register(exit_handler)
It seems to work OK, and runs the motor controller on my Pi (just - due to dodgy wiring
)
But when I run it, I get this as the output:
[wilf#Pi MovementCommands]$ 1/forward.py
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
^CTraceback (most recent call last):
File "1/forward.py", line 17, in <module>
time.sleep(5)
KeyboardInterrupt
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
[wilf#Pi MovementCommands]$
For some reason it seems to be looping the exit_handler nearly as many times as the main bit loops - how can I stop it doing this - it can take ages to exit if it has be running for a while, as it loops the cleanup command. (The main bit does not have to loop, but it is useful as visual indication that it is doing something - could I possibly just loop the print bit...).
I also seem to be unable to stop the traceback of atexit being displayed, without having to use except KeyboardInterrupt. This would be useful anyway, but I may later want to automate it by running it from a Bash script (yes I could integrate it into it, but I don't want to).
You are re-registering the atexit handler each time your loop iterates. Python calls the function as many times as it has been registered.
Register the function just once. You don't need to call sys.exit(0) in your atexit function either, you are already exiting your program.
Corrected program:
import time, atexit
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
def exit_handler():
GPIO.cleanup()
print 'STOPPED'
atexit.register(exit_handler)
while True:
GPIO.output(11, True)
GPIO.output(16, True)
print "Forward"
time.sleep(5)
Alternatively, catch the KeyboardInterrupt and clean up in the exception handler:
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
try:
while True:
GPIO.output(11, True)
GPIO.output(16, True)
print "Forward"
time.sleep(5)
except KeyboardInterrupt:
pass # silence the interrupt
finally:
GPIO.cleanup()
print 'STOPPED'
The finally block calls GPIO.cleanup() regardless of what exception occurred, the except KeyboardInterrupt just silences the keyboard interrupt exception.
Note that the traceback was never in the atexit() function, it is the reason the program is exiting and why atexit() is called at all. In other words, the exception preceeds the atexit() function call.

Categories