Raspberry Pi : Plusieurs fonctions avec un seul bouton

Dans un des premiers articles de hardware-libre, j’ai expliqué comment ajouter un bouton d’extinction ou de reboot au Raspberry Pi.

Nous allons aujourd’hui améliorer ce système en rajoutant une possibilité bien pratique : des fonctions différentes selon la durée d’appui sur le bouton.

Pour faire suite à l’article précédent, nous aurons désormais 2 fonctions :
– reboot sur un appui entre 1 et 4 secondes,
– arrêt complet sur un appui long de plus de 4 secondes
Le code fourni ici rajoute une autre possibilité d’appui très court, entre 0.2 et 1 seconde, mais nous l’utiliserons simplement afin d’éviter un reboot en cas d’appui malencontreux.

Bien que ce ne soit pas obligatoire, nous allons modifier un peu le circuit. La raison est qu’en ayant le bouton directement entre le GPIO et le GND, si une erreur dans un script configure le GPIO en sortie, et qu’on le presse à ce moment là, on va créer un court-circuit au niveau du GPIO, qui risque de griller.
Nous allons donc supprimer complètement ce risque en rajoutant simplement une résistance de pull-up. Voici un petit schéma rapide de cette modification :
butt-schem
La résistance de pull-up permet de limiter le courant si le GPIO est malencontreusement configuré en sortie, et le bouton pressé.

Coté code, les commentaires sont je pense suffisant pour expliquer le tout :

#!/usr/bin/env python2.7

from time import sleep
import subprocess
import RPi.GPIO as GPIO

# On choisit le GPIO 23 (pin 16) pour notre bouton
CHANNEL = 23 

# On definit nos durees
long_press = 1
very_long_press = 4

# on met RPi.GPIO en mode notation BCM
GPIO.setmode(GPIO.BCM)

# on initialise le GPIO 23 en mode entree
GPIO.setup(CHANNEL, GPIO.IN, pull_up_down=GPIO.PUD_UP)

# notre fonction extinction
def shutdown():
	subprocess.call(['shutdown -h now "Arret du systeme par bouton GPIO" &'], shell=True)

# notre fonction reboot
def reboot():
	subprocess.call(['sudo reboot "Reboot du systeme par bouton GPIO" &'], shell=True)

# notre fonction de gestion du bouton
def system_button(CHANNEL):
	# cette variable servira a stocker le temps de pression
	button_press_timer = 0

	while True:
			if (GPIO.input(CHANNEL) == False) : # le bouton a ete presse...
				button_press_timer += 0.2 # ... on enregistre le temps que cela dure

			else: # le bouton a ete relache, on compte combien de temps cela a dure
				if (button_press_timer > very_long_press) :
					print "very long press : ", button_press_timer
					shutdown()

				elif (button_press_timer > long_press) :
					print "long press : ", button_press_timer
					reboot()

				elif (button_press_timer > 0.2):
					print "short press : ", button_press_timer

				button_press_timer = 0
			# on attend 0.2 secondes avant la boucle suivante afin de reduire la charge sur le CPU
			sleep(0.2)

# on met le bouton en ecoute par interruption, detection falling edge sur le canal choisi, et un debounce de 200 millisecondes
GPIO.add_event_detect(CHANNEL, GPIO.FALLING, callback=system_button, bouncetime=200)

# ici vous pouvez mettre du code qui sera execute normalement, sans influence de la fonction bouton
try:
	while True:
		# faites ce qui vous plait
		sleep (2)

# on reinitialise les ports GPIO en sortie de script
except KeyboardInterrupt:
	GPIO.cleanup()
GPIO.cleanup()

Vous remarquerez qu’on a introduit le module subprocess à la place du module os pour appeler nos commandes shutdown et reboot, ce qui est bien plus efficace « pythoniquement » parlant. Je vous invite à suivre ce lien pour en apprendre plus à ce sujet.
L’option « bouncetime=200 » de la fonction « GPIO.add_event_detect », quant à elle, effectue un premier filtrage du bouton (pour éviter par exemple les micro-relachements lors d’un appui).

Pour améliorer le système, on pourrait également par exemple rajouter une led,
– allumée pour signaler que le Pi est démarré et prêt,
– clignotement rapide pour signaler un reboot,
– clignotement lent pour signaler une extinction,
……
A vous de faire preuve d’imagination, tout est possible 😉

Edit :
– En cas d’erreur du type « TypeError: set_callback() takes at most 3 arguments (4 given) » lors de l’execution du script, il vous faudra installer / mettre à jour le composant RPi.GPIO :

sudo apt-get update
sudo apt-get install python-dev
sudo apt-get install python-rpi.gpio

– En cas d’erreur avec la fonction reboot (usage: reboot [-n] [-w] [-d] [-f] [-h] [-i] …), il suffit de modifier la ligne 26 :

subprocess.call(['sudo reboot -f "Reboot du systeme par bouton GPIO" &'], shell=True)

ou encore :

subprocess.call(['sudo shutdown -r now "Reboot du systeme par bouton GPIO" &'], shell=True)

Merci à Djeremaille 😉

26 thoughts on “Raspberry Pi : Plusieurs fonctions avec un seul bouton

  1. Djeremaille
    17/03/2014 at 21 h 02 min

    Bonjour,
    J’ai du mal à comprendre où placer la résistance et d’où vient le +3.3v. Par rapport au schéma initial sur la breadboard ça se présente comment?

    1. 18/03/2014 at 0 h 58 min

      le 3,3 V correspond au 3,3V du Pi (Pin 1 des GPIO).
      Pin 1 -> résistance -> GPIO 23 (pin 16) -> bouton -> masse (pin 6 par ex)

      1. Djeremaille
        18/03/2014 at 1 h 42 min

        Ah d’accord. C’est un peu plus clair.
        Je testerai ça demain.
        étant en train d’installer un raspberry dans le coffre de ma voiture, le bouton sur le tableau de bord va m’être bien utile.

  2. Djeremaille
    18/03/2014 at 13 h 51 min

    Bon alors je suis en train de tester mais ça ne fonctionne pas.
    Il a déjà fallu que j’enlève les accents dans les commentaires. Ca, pas problème.
    Ensuite ça c’est corsé:
    GPIO.add_event_detect(CHANNEL, GPIO.FALLING, callback=system_button, bouncetime=200)
    TypeError: set_callback() takes at most 3 arguments (4 given)

    J’en ai déduit qu’il y avait un argument de trop: j’ai enlevé bouncetime=200.
    Ouf, plus d’erreurs au démarrage du script.
    Par contre lorsque j’appuie sur le bouton, rien ne se passe à part ce message:
    TypeError: system_button() takes exactly 1 argument (0 given)

    Je précise que je n’y connais rien.
    Merci de votre aide.

    1. Captain Stouf
      18/03/2014 at 14 h 41 min

      Bonjour,
      Effectivement j’avais laissé quelques accents indésirables dans les commentaires, c’est corrigé, merci.
      Je viens de copier-coller le code directement dans un script, sur mon Pi, en ayant uniquement commenté les lignes shutdown() et reboot() (lignes 40 et 44). Je peux confirmer qu’il fonctionne parfaitement de mon coté.
      L’erreur « TypeError: system_button() takes exactly 1 argument (0 given) » indique qu’il manque un argument à la fonction system_button() après votre tentative de correction. A première vue, je dirai que l’erreur vient de la variable CHANNEL dans votre script.
      Pouvez vous essayer de copier le code et faire un test sans modification (à part commenter les lignes 40 et 44 comme je l’ai fait), et m’indiquer les erreurs s’il y en a ?

  3. Djeremaille
    18/03/2014 at 15 h 28 min

    Il y a du mieux, c’est presque ça. Il fallait que je mette à jour RPi.GPIO.
    Mon bouton ne fonctionne que pour l’arrêt et pas pour le reboot.
    J’ai ce message:
    long press : 1.8
    usage: reboot [-n] [-w] [-d] [-f] [-h] [-i]
    -n: don’t sync before halting the system
    -w: only write a wtmp reboot record and exit.
    -d: don’t write a wtmp record.
    -f: force halt/reboot, don’t call shutdown.
    -h: put harddisks in standby mode.
    -i: shut down all network interfaces.

    1. Captain Stouf
      18/03/2014 at 15 h 38 min

      Vous devriez pouvoir résoudre cette erreur en modifiant la fonction reboot (ligne 26) :
      subprocess.call(['sudo reboot "Reboot du systeme par bouton GPIO" &'], shell=True)
      deviendrait
      subprocess.call(['sudo reboot -f "Reboot du systeme par bouton GPIO" &'], shell=True)

      J’essaie actuellement de trouver pourquoi vous avez besoin d’utiliser l’argument -f de votre coté mais je n’ai pour le moment pas de réponse claire.
      Voici une page explicative des commandes halt, reboot et shutdown : http://www.tutorialspoint.com/unix_commands/halt.htm

  4. Djeremaille
    18/03/2014 at 15 h 44 min

    Dernière fois que je vous embête j’espère.
    Problème résolu en remplaçant « sudo reboot » par « shutdown -r now »

    1. Captain Stouf
      18/03/2014 at 15 h 50 min

      Il n’y a pas de problème, on est là pour ça justement 😉
      J’ai mis à jour l’article avec ces quelques ajustements, merci !

  5. Djeremaille
    18/03/2014 at 20 h 33 min

    Il faut préciser que je suis sous Volumio qui est basée sur Raspbian mais doit comporter quelques différences.
    En tous cas merci pour cet article!
    Et pour le prochain avec l’ajout d’une led pour prévenir de l’extinction ;).
    Ca me serait très utile dans la voiture. J’ai même prévu le câble :)

    1. Captain Stouf
      18/03/2014 at 20 h 44 min

      Pour la LED, c’est facile et je peux même répondre ici :
      GPIO – Resistance (200~400 ohm) – LED – GND

      Pour le code (disons que la LED est sur le GPIO 21) :

      GPIO.setup(21, GPIO.OUT) ## Configure le GPIO 21 en sortie

      GPIO.output(21,True) ## Allume la LED
      GPIO.output(21,False) ## Eteint la LED

      Il ne reste plus qu’à mettre à jour les fonctions reboot() et shutdown() dans le script pour allumer la LED juste avant de lancer les commandes shutdown / reboot, et le tour est joué.

      1. Djeremaille
        19/03/2014 at 21 h 55 min

        Merci pour la piste. Je vais étudier ça. En fait je voudrais une led qui s’allume 1 sec ou 2 à la toute fin de l’extinction, pour savoir quand je peux éteindre les convertisseur 12/220v.
        Sinon j’ai tout intégré dans ma voiture et je suis particulièrement satisfait du résultat.
        Je vais peut-être faire un tuto sur instructables quand je serai motivé.

  6. 08/05/2014 at 10 h 02 min

    Bonjour et merci beaucoup pour le partage de vos travaux !

    Ce code fonctionne à merveille,
    mais je rencontre une difficulté lorsque j’utilise deux boutons ou plus.
    En effet, dés que j’insère ce qu’il faut pour un autre bouton et surtout sa ligne :
    GPIO.add_event_detect(CHANNEL2, GPIO.FALLING, callback=fonction_autre, bouncetime=200)
    il se passe la chose suivante :

    Bouton 1 appelle system_button
    Bouton 2 appelle fonction_autre

    CAS 1 :
    Appuie sur le Bouton 1 > affiche la durée, OK
    Appuie sur le Bouton 2 > affiche la durée, OK
    Appuie sur le Bouton 1 > affiche la durée, OK
    Appuie sur le Bouton 1 > affiche la durée, OK
    Appuie sur le Bouton 1 > affiche la durée, OK
    Appuie sur le Bouton 2 > affiche la durée, OK
    Appuie sur le Bouton 2 > affiche la durée, OK
    Ça roule dans ce cas !

    CAS 2 :
    Appuie sur le Bouton 1 > affiche la durée, OK
    Appuie sur le Bouton 1 > affiche la durée, deux fois (et même plus si d’autres boutons sont configurés)
    Appuie sur le Bouton 1 > rebelote…
    Appuie sur le Bouton 2 > aucun effet…
    Visiblement quelque chose bloque le programme, j’ai passé la journée d’hier à chercher l’origine du problème sans succès.
    Auriez-vous une idée de ce qu’il se passe ?

    1. 06/07/2014 at 17 h 30 min

      J’ai fini par trouver la solution, voici mon code fonctionnel

      #!/usr/bin/env python2.7
      # coding: utf-8
      # on importe les modules nécessaires
      import time
      import os, sys
      import RPi.GPIO as GPIO

      GPIO.setmode(GPIO.BCM)
      CHANNEL = 17
      CHANNEL2 = 18
      GPIO.setup(CHANNEL, GPIO.IN)
      GPIO.setup(CHANNEL2, GPIO.IN)
      long_press = 1
      very_long_press = 3

      def bouton17(a):
      button_press_timer = 0
      GPIO.remove_event_detect(CHANNEL) # empéche le bouton d’etre détecté x fois de plus
      while True:
      if(GPIO.input(a) == GPIO.LOW ): # bouton appuyé
      button_press_timer += 0.2 # incrémente tant que le bouton est maintenu
      print button_press_timer
      print a

      else: # bouton relaché
      if (button_press_timer > very_long_press):
      print »very long press : « , button_press_timer
      elif (button_press_timer > long_press):
      print »long press : « , button_press_timer
      elif (button_press_timer > 0):
      print »short press : « , button_press_timer
      button_press_timer = 0
      time.sleep(0.2)

      def bouton18(b):
      button_press_timer = 0
      GPIO.remove_event_detect(CHANNEL2) # empéche le bouton d’etre détecté x fois de plus
      while True:
      if(GPIO.input(b) == GPIO.LOW ): # bouton appuyé
      button_press_timer += 0.2 # incrémente tant que le bouton est maintenu
      print button_press_timer
      print b

      else: # bouton relaché
      if (button_press_timer > very_long_press):
      print »very long press : « , button_press_timer
      elif (button_press_timer > long_press):
      print »long press : « , button_press_timer
      elif (button_press_timer > 0):
      print »short press : « , button_press_timer
      button_press_timer = 0
      time.sleep(0.2)

      GPIO.add_event_detect(CHANNEL, GPIO.FALLING, callback=bouton17, bouncetime=100)
      GPIO.add_event_detect(CHANNEL2, GPIO.FALLING, callback=bouton18, bouncetime=100)

      while True:

      time.sleep (2)
      GPIO.cleanup()

  7. David
    30/07/2014 at 16 h 19 min

    Bonjour, je viens de me lancer dans le raspberry pi et j’ai voulu installer Retropie pour émuler différentes consoles. J’ai fait le tuto indiqué plus haut et le bouton fonctionne a merveille sauf que retropie ne se lance pas et je reste bloqué sur une page de démarrage avec un trait blanc qui clignote :(
    j’ai refait l’installation 4 ou 5 fois et toujours pareil. Est ce que quelqu’un peut m’aider?
    Merci beaucoup

    1. Captain Stouf
      30/07/2014 at 22 h 58 min

      Bonjour, je connais encore peu RetroPie, mais

      je reste bloqué sur une page de démarrage avec un trait blanc qui clignote

      ce n’est pas suffisant pour t’aider. Retropie fonctionne si tu le lances directement sans le bouton ?

      1. David
        30/07/2014 at 23 h 22 min

        Merci de votre réponse rapide :) retropie fonctionne parfaitement sans le bouton.

        1. David
          30/07/2014 at 23 h 29 min

          dans la commande sudo nano /etc/rc.local
          juste avant la ligne # Print the IP address, j’insére la commande sudo python /home/pi/bouton_extinction.py comme le dit le tuto et lors du reboot quand on est sur le chargement cela s’arrete avec un trait blanc juste a cet endroit là. j’essaye d’etre clair mais je suis vraiment débutant :/

        2. Captain Stouf
          30/07/2014 at 23 h 30 min

          Ok.
          – Quelle est la commande que tu utilises pour le lancer, sans le bouton ?
          – Avec le bouton, tu le vois qui commence à se lancer, ou rien du tout ?
          – Pourrais tu également coller ton script sur pastebin pour que je jette un oeil ?

  8. David
    30/07/2014 at 23 h 36 min

    j’ai telechargé l’image de retropie donc il se lance tout seul.
    avec le bouton ca commence a se lancer puis rien du tout
    le script est le meme que vous avez fourni, j’ai copier coller

    1. David
      30/07/2014 at 23 h 54 min

      pour faire simple:
      -lorsque je démarre rétropie sans avoir fait de manip pour le bouton cela fonctionne nickel.

      -lorsque je rentre le code plus haut (ou celui avec juste la fonction arrêt de la page précédente) je reboot et la le chargement s’arrete juste apres:
      [ok] starting OpenBSD Secure Shell server: sshd

      c’est comme si la suite ne se lançait pas

      -j’ai mis le code tel quel, j’ai rien changé.

      -lorsque le trait blanc clignote je peut éteindre quand même avec le bouton.

      1. Captain Stouf
        31/07/2014 at 0 h 51 min

        dans ton /etc/rc.local, essaies déja de remplacer
        sudo python /home/pi/bouton_extinction.py
        par
        sudo python /home/pi/bouton_extinction.py &

        1. David
          31/07/2014 at 10 h 08 min

          Mille merci Captain Stouf !!!! c’etait bien ça qu’il manquait! maintenant cela fonctionne parfaitement.
          Vraiment merci pour votre aide et votre réactivité :)

  9. Jeremy
    05/04/2015 at 13 h 40 min

    Bonjour,

    Merci pour ce tuto !
    J’ai un Raspberry Pi B+ et je viens de m’apercevoir que j’ai oublié d’acheter la résistance de 10k Ohm…

    J’ai lu qu’on pouvait activer le pull-up ou le pull-down sur les GPIO du RPi.
    Pourquoi ne pas passer par cette option au lieu d’utiliser une résistance de 10k ?

    Merci !

    1. Captain Stouf
      05/04/2015 at 16 h 59 min

      Bonjour,
      En réalité, le Raspberry a des (faibles) résistances pull-up de 1.8kOhm uniquement sur les 2 pins I2C. En théorie, un bouton cablé sur une de ces 2 pins pourrait se passer de pull-up externe, mais par habitude et précaution, je préfère en rajouter une.

      https://projects.drogon.net/raspberry-pi/wiringpi/special-pin-functions/

Laisser un commentaire