jahweejoseph
Posts: 22
Joined: Tue Jan 12, 2016 12:50 am

Long Press Shutdown Button

Mon Jan 18, 2016 4:55 pm

Hi I am trying to sue a Gpio pinThat call a python script to shutdown my Pi. The problem is I need it to be a long press so that accidental shutdown doesnt occur. I used the following script

Code: Select all



import RPi.GPIO as GPIO
import os
import time

gpio_pin_number=12


GPIO.setmode(GPIO.BCM)
GPIO.setup(gpio_pin_number, GPIO.IN, pull_up_down=GPIO.PUD_UP)

#try:
GPIO.wait_for_edge(gpio_pin_number, GPIO.FALLING, bouncetime=300)
start_time = time.time()
GPIO.wait_for_edge((gpio_pin_number, GPIO.RISING, bouncetime=300)
    # calculate elapsed time
time_elapsed = time.time() - start_time

if time_elapsed >= 3:
        os.system("sudo shutdown -h now")
        #Send command to system to shutdown
 
Problem is that the script will shut down on two preeses of the button instead of a long press only , once the presses are some time apart .. Any Help?

paulv
Posts: 558
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: Long Press Shutdown Button

Mon Jan 18, 2016 6:22 pm

The following script works, try that.

Code: Select all

    #!/usr/bin/env python2.7
    import RPi.GPIO as GPIO
    import subprocess
    from time import sleep

    GPIO.setmode(GPIO.BCM) # use GPIO numbering
    GPIO.setwarnings(False)

    INT = 12    # GPIO-12 button interrupt to shutdown procedure

    # use a weak pull_up to create a high
    GPIO.setup(INT, GPIO.IN, pull_up_down=GPIO.PUD_UP)

    def main():

        while True:
            # set an interrupt on a falling edge and wait for it to happen
            GPIO.wait_for_edge(INT, GPIO.FALLING)
            # we got here because the button was pressed.
            # wait for 3 seconds to see if this was deliberate
            sleep(3)
            # check the button level again
            if GPIO.input(INT) == 0:
                # still pressed, it must be a serious request; shutdown Pi
                subprocess.call(['poweroff'], shell=True, \
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)


    if __name__ == '__main__':
        main()

User avatar
paddyg
Posts: 2359
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Long Press Shutdown Button

Mon Jan 18, 2016 8:19 pm

or better, use add_event_detect() to run a separate function which has the loop inside it that keeps checking if the button is still down for several seconds and only switches off it is. That way your main python loop can do other things (involving alternative GPIO etc)
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

paulv
Posts: 558
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: Long Press Shutdown Button

Tue Jan 19, 2016 5:55 am

Paddy,

Add_event_detect() has issues with buttons. It will generate multiple hits despite using the debounce in software or even in hardware.
You have to jump through some hoops to make it work, as you did yourself. ;)

User avatar
elParaguayo
Posts: 1943
Joined: Wed May 16, 2012 12:46 pm
Location: London, UK

Re: Long Press Shutdown Button

Tue Jan 19, 2016 8:29 am

I've not had any issues with buttons and add_event_detect

I've handled long presses before by having a counter in a separate thread and it works perfectly.
RPi Information Screen: plugin based system for displaying weather, travel information, football scores etc.

User avatar
paddyg
Posts: 2359
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Long Press Shutdown Button

Tue Jan 19, 2016 9:22 am

@paulv, yes there are certainly issues if there isn't a clean switch on or off (as I found with the quadrature encoder). However in this case that doesn't matter too much as triggered function simply loops, checking that the button is still down. Along the lines of

Code: Select all

def on_event():
  for i in range(30):
    time.sleep(0.1)
    if GPIO.input(gpio_pin_number) == 1: # button not held down
      return
  switch_off_computer()
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

paulv
Posts: 558
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: Long Press Shutdown Button

Wed Jan 20, 2016 10:49 am

Well, I've said A, so I need to back that up with B.

I've had issues with multiple hits on add_event_detect since somewhere in the middle of 2013, when a kernel update changed it from working well, to working not so well. I don't have further details, but I've been avoiding this feature while using buttons when I could.

It is possible however to make it work, with some additional code.
Here is an example of it not working correctly:

Code: Select all

[email protected] ~ $ sudo ./button_test2.py
Button test started
0       Button level is :  0
1       Button level is :  1
2       Button level is :  0
3       Button level is :  0
4       Button level is :  1
5       Button level is :  0
6       Button level is :  0
7       Button level is :  0
8       Button level is :  1
9       Button level is :  0
10      Button level is :  0
11      Button level is :  0
12      Button level is :  1
13      Button level is :  0
14      Button level is :  0
15      Button level is :  0
16      Button level is :  0
17      Button level is :  1
Whenever you see a Button level of 1, it is representing a double hit. What I mean by that is that I press the button once, and there are two replies registered by the code. What also happens is that with only one press of the button, there are two events, but the level is good, so that does not show here, unfortunately.
Here is the code, following the "book" :

Code: Select all

#!/usr/bin/env python2.7
#-------------------------------------------------------------------------------
# Name:         button_test2
#
# Purpose:      This program test the GPIO code with a button
#
# Author:       Paul Versteeg
#
# Created:     15-06-2015, revised on 18-12-2015
# Copyright:   (c) Paul 2015
# Licence:     <your licence>
#-------------------------------------------------------------------------------

import RPi.GPIO as GPIO
from time import sleep

GPIO.setmode(GPIO.BCM) # use GPIO numbering
GPIO.setwarnings(True)

Button = 22    # Any GPIO port without special functions or pull's

event_counter = 0

def button_svr(pin):
    global event_counter

    print event_counter, "\tButton level is : ", GPIO.input(Button)
    event_counter += 1
    return # not needed, here for clarity


# define the Button pin as input, use a pull-up to create a high to low transition
GPIO.setup(Button, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# and define the event with the service routine
# the bouncetime is in milliseconds, sounds not a lot, 
# but with the speed of the Pi, it is. 
# The complete service routine duration is less than 2Msec.
GPIO.add_event_detect(Button, GPIO.FALLING, callback=button_svr, bouncetime=20)


def main():
    print "Button test started"
    try:
        while True: # loop forever while waiting for the interrupt
            pass

    except KeyboardInterrupt:
        pass
    finally:
        print "\nClosing the used Port"
        GPIO.cleanup(Button)

if __name__ == '__main__':
    main()
Changing the bouncetime to a larger value does not seem to improve anything. So what can we do to make this more reliable.
First of all, I have been using the "internal" pull-up feature to create a high to low signal when I press the button. So in this case, the button is connected between the GPIO pin and Ground. This resistor is around 50K, so the pull is very soft. (3.3V/50K=66 uA) This could create extra bounce, i.e. jitter, and that can cause false triggers. So the next test is to not use the internal pull, but use a 1K resistor between the GPIO pin and the 3V3 supply. (creating a 3.3mA "pull") The only change in the code is this:

Code: Select all

GPIO.setup(Button, GPIO.IN)
Unfortunately, no improvement. We can get more serious by adding a capacitor across the button. This will be charged up to 3V3, and discharged to ground by the press of the button. The voltage from this level will "slowly" rise due to the 1K resistor, so the net effect is that it will "dampen" the jitter around the ground level, because that could cause false triggering.

Using a capacitor of 100nF does help, there are fewer bad triggers. Going to a more drastic value, by using a 10uF capacitor seems to solve the problem. The negative side effect is however that the reaction time of the button is reduced. It takes almost 100 mSec for the level to go from ground back to 3V3. That may or may not be an issue, if it is you can lower the value.

So hardware can solve the problem, but can we do it in software?
Yes, we can! We'll go back to our internal soft pull-up, so we remove the 1K resister and the capacitor, and we change the service routine as follows:

Code: Select all

def button_svr(pin):
    global event_counter
    sleep(0.001) # 1mSec
    if GPIO.input(Button) == 0:
        print event_counter, "\tButton level from pin %s is : "%pin, GPIO.input(Button)
        event_counter += 1
The first thing you'll notice is the sleep(0.001) (in seconds) waiting time, to let the bounce subside. Then we read the input pin again, and only when it is at ground (0), we count it as a valid event, otherwise, we return, effectively throwing the event in the bit bucket. I also removed the software bouncetime, because it is no longer needed.
So here is the complete and working version:

Code: Select all

#!/usr/bin/env python2.7
#-------------------------------------------------------------------------------
# Name:         button_test2
#
# Purpose:      This program test the GPIO code with a button
#
# Author:       Paul Versteeg
#
# Created:     15-06-2015, revised on 18-12-2015
# Copyright:   (c) Paul 2015
# Licence:     <your licence>
#-------------------------------------------------------------------------------

import RPi.GPIO as GPIO
from time import sleep

GPIO.setmode(GPIO.BCM) # use GPIO numbering
GPIO.setwarnings(True)

Button = 22    # Any GPIO port without special functions or pull's
Trigger = 23

event_counter = 0

def button_svr(pin):
    global event_counter
    sleep(0.001) # 1mSec
    if GPIO.input(Button) == 0:
        print event_counter, "\tButton level from pin %s is : "%pin, GPIO.input(Button)
        event_counter += 1
    return

# define the pin as input
GPIO.setup(Button, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(Trigger, GPIO.OUT)
GPIO.add_event_detect(Button, GPIO.FALLING, callback=button_svr)

def main():

    print "Button test started"

    try:
        while True:
            pass

    except KeyboardInterrupt:
        pass
    finally:
        print "\nClosing the used Port"
        GPIO.cleanup(Button)

if __name__ == '__main__':
    main()
I said working, but let me define that further. I pressed the button 125 times, carefully checking for false bounces. When none appeared, I called it successful, but you'll never know for sure, can you?

There is more. You need to reduce the amount of code (or better time spent) inside the service routine as much as possible, and offload that to another routine. This reduces the chances that the interrupt service routine gets interrupted. To solve that issue is for another time.

So, if you decide to use the add_event_detect feature, I suggest you do a thorough test of your hardware and code to see if it indeed does what you think it should do.

Enjoy!

paulv
Posts: 558
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: Long Press Shutdown Button

Thu Jan 21, 2016 10:05 am

I did some more work on this problem, and reported that in another post. Have a look here on a possible solution to your problem, or to see the proof of the bug in the GPIO code.
viewtopic.php?f=28&t=131440&p=888960#p888960
Success!

jahweejoseph
Posts: 22
Joined: Tue Jan 12, 2016 12:50 am

Re: Long Press Shutdown Button

Fri Jan 22, 2016 8:44 am

paulv wrote:The following script works, try that.

Code: Select all

    #!/usr/bin/env python2.7
    import RPi.GPIO as GPIO
    import subprocess
    from time import sleep

    GPIO.setmode(GPIO.BCM) # use GPIO numbering
    GPIO.setwarnings(False)

    INT = 12    # GPIO-12 button interrupt to shutdown procedure

    # use a weak pull_up to create a high
    GPIO.setup(INT, GPIO.IN, pull_up_down=GPIO.PUD_UP)

    def main():

        while True:
            # set an interrupt on a falling edge and wait for it to happen
            GPIO.wait_for_edge(INT, GPIO.FALLING)
            # we got here because the button was pressed.
            # wait for 3 seconds to see if this was deliberate
            sleep(3)
            # check the button level again
            if GPIO.input(INT) == 0:
                # still pressed, it must be a serious request; shutdown Pi
                subprocess.call(['poweroff'], shell=True, \
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)


    if __name__ == '__main__':
        main()
This doesn not work , but thanks for the effort.

paulv
Posts: 558
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: Long Press Shutdown Button

Fri Jan 22, 2016 9:17 am

jahweejoseph, can you elaborate why it does not work for you?
There must be something that causes it not to work in your environment, and to help you find that out, we need a bit more information.
If my code is embedded in your code, can you post it?
Are you using a hardware de-bouce circuit for your button? If not, just put a 100nF capacitor across the switch to ground. A higher value will work to in your case.
Can you post the results of :
uname -a
And add this to your Python program :

Code: Select all

print "GPIO Version = ", GPIO.VERSION
and show the results?
To test the code, make these two changes (comment out the subprocess call, and add a print statement instead):

Code: Select all

            if GPIO.input(INT) == 0:
                # still pressed, it must be a serious request; shutdown Pi
                print "Valid button press - shutdown Pi"
                # subprocess.call(['poweroff'], shell=True, \shutdown
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Success!

jahweejoseph
Posts: 22
Joined: Tue Jan 12, 2016 12:50 am

Re: Long Press Shutdown Button

Sat Jan 23, 2016 11:19 pm

jahweejoseph wrote:
paulv wrote:The following script works, try that.

Code: Select all

    #!/usr/bin/env python2.7
    import RPi.GPIO as GPIO
    import subprocess
    from time import sleep

    GPIO.setmode(GPIO.BCM) # use GPIO numbering
    GPIO.setwarnings(False)

    INT = 12    # GPIO-12 button interrupt to shutdown procedure

    # use a weak pull_up to create a high
    GPIO.setup(INT, GPIO.IN, pull_up_down=GPIO.PUD_UP)

    def main():

        while True:
            # set an interrupt on a falling edge and wait for it to happen
            GPIO.wait_for_edge(INT, GPIO.FALLING)
            # we got here because the button was pressed.
            # wait for 3 seconds to see if this was deliberate
            sleep(3)
            # check the button level again
            if GPIO.input(INT) == 0:
                # still pressed, it must be a serious request; shutdown Pi
                subprocess.call(['poweroff'], shell=True, \
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)


    if __name__ == '__main__':
        main()
This doesn not work , but thanks for the effort.
Linux raspberrypi 3.18.5-v7+ #1 SMP PREEMPT Fri Feb 6 23:06:57 CET 2015 armv7l GNU/Linux

jahweejoseph
Posts: 22
Joined: Tue Jan 12, 2016 12:50 am

Re: Long Press Shutdown Button

Sat Jan 23, 2016 11:47 pm

Thanks Guys ... Finally git it to work exactly how i wanted ... very helpful community!

hippy
Posts: 5947
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: Long Press Shutdown Button

Sun Jan 24, 2016 1:42 pm

paulv wrote:

Code: Select all

GPIO.wait_for_edge(INT, GPIO.FALLING)
sleep(3)
if GPIO.input(INT) == 0:
  # still pressed, it must be a serious request; shutdown Pi
There seems to be a flaw in that algorithm. If there is an accidental short push and then another accidental short push 3 seconds later, that could be interpreted as a single 3 second long push.

I would have simply repeatedly polled the input, checking the input button state, zeroing or incrementing a time held variable, something like ...

Code: Select all

pushTime = 0
while pushTime < 3000:
  time.sleep(0.1)
  if GPIO.input(Button) == 0 : pushTime = pushTime + 100 
  else                       : pushTime = 0
print "Button has been pushed and held for 3000 ms"
Last edited by hippy on Sun Jan 24, 2016 1:47 pm, edited 1 time in total.

gordon77
Posts: 4164
Joined: Sun Aug 05, 2012 3:12 pm

Re: Long Press Shutdown Button

Sun Jan 24, 2016 1:45 pm

Here's some code l did, it lights a led but could call a shutdown...

viewtopic.php?f=32&t=133621&p=890444#p890444

paulv
Posts: 558
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: Long Press Shutdown Button

Sun Jan 24, 2016 2:24 pm

Yes hippy, you are right - of course.
I use the same in some of my routines, especially using one button to do different tasks, based on the amount of time pressed.

When all you want is a controlled powerdown with that button, the solution I showed is simple and should be sufficient, albeit not perfect.

Tks for contributing your solution.

(PS are you the same hippy from the PICAXE Forum? (;-))

Return to “Python”