Pulsante di shutdown/reboot per Raspbery Pi

IMG_5340
Per spegnere un Raspberry Pi nella maniera giusta si deve fare il login e dare il comando ‘sudo shutdown -h now’. In questo modo il sistema si arresterà in modo pulito e non rischieremo di corrompere il file system della scheda SD o di altri dischi collegati. Poco male se il Raspberry Pi è connesso a monitor e tastiera. Ma se invece si tratta di dispositivo headless, al quale ci si connette esclusivamente da remoto via ssh, può capitare di dover accendere un altro computer solo per arrestarlo… una vera scocciatura!

“Sarebbe bello” mi sono detto “avere sul Raspy un bel pulsante per lanciare la procedura di shutdown. Dato che sul Raspberry Pi ci sono diversi pin GPIO (General Purpose Input Output) la cosa non sembra affatto complicata. Vediamo se c’è già qualcosa in rete!”. Ed in effetti di esempi ce ne sono parecchi: questo, questo oppure questo, tanto per citarne alcuni.

Ma a uno trovavo un difetto, a un altro trovavo un altro difetto… insomma: nessuno che mi piacesse fino in fondo. Per questo ho pensato di realizzare un pulsante, con relativa circuiteria e software, diverso da tutti gli altri e che avrebbe dovuto possedere i seguenti requisiti:

  1. avere una duplice funzione: pressione breve (ad esempio < 3 s) per innescare il reboot; pressione lunga (>= 3 s) per innescare lo shutdown;
  2. impiegare il minimo di risorse di sistema possibile, ed evitare completamente il polling (ossia la tecnica – dispendiosa in termini di cicli di CPU – di testare ciclicamente lo stato del pulsante per vedere se sia premuto o meno);
  3. avere un feedback per l’utente: il Raspberry Pi deve mostrare in qualche modo se ha intercettato la pressione del pulsante, e quale tipo di pressione (breve o lunga) ha riconosciuto.

Con questi tre requisiti in mente, e dopo aver studiato i vari esempi trovati in rete, ho progettato e realizzato il “mio” pulsante di spegnimento.

Il circuito

Il circuito è veramente semplice, anche per i principianti di elettronica. Si tratta di collegare un pulsante tra un pin GPIO qualsiasi (da configurare via software come ingresso) e massa e, per il feedback, collegare la serie di un LED e un resistore da 220 Ω tra un altro pin GPIO (da configurare via software come uscita) e massa. Lo schema dei collegamenti è il seguente:

shutdown_pi_bb

, dove ho usato il pin 11 (cavo blu) per il LED e il pin 12 (cavo rosso) per il pulsante. I due cavi neri sono collegati entrambi a massa (su due pin diversi, ma entrambi i pin, internamente al Raspberry Pi, sono collegati alla stessa massa). Più avanti si vedrà come identificare i pin.

I componenti utilizzati sono:

  1. un Raspberry Pi
  2. un pulsante a pressione
  3. un LED (Light Emitting Diode)
  4. un resistore da 220 Ω
  5. una breadboard da prototipazione (opzionale)
  6. vari jumper assortiti (femmina/femmina se senza breadboard, oppure maschio/femmina se con la breadboard)

A proposito dei jumper: magari qualcuno ha Arduino e avrà già pensato di usare gli stessi jumper… beh, non si può fare: i pin GPIO del Raspberry Pi sono maschi, mentre su Arduino gli IO sono femmine. I jumper maschio/maschio che si usano tra Arduino e breadboard non possono essere usati con il Raspberry Pi (e viceversa).

Che ci sta a fare quel resistore in serie al LED? Serve a limitare la corrente che scorre nel LED in modo da non vederlo “fiammare” alla prima accensione. Con un resistore più grande si ha meno corrente, quindi minor luminosità e, ovviamente, con un resistore più piccolo si ha più corrente e più luminosità (ma con il rischio di bruciare il LED). Occhio al verso del LED: i LED, come tutti i diodi, fanno passare corrente in un verso solo: quello che va dall’anodo (terminale lungo) al catodo (terminale corto). Per far sì che  possa scorrere corrente e il LED si accenda, occorre collegare il catodo a massa; se si collega alla rovescia (ossia con l’anodo a massa) non si rischia nulla, ma il LED semplicemente non si accenderà.

Io ho trovato le varie componenti elettroniche in un kit da hobbista (veramente ricco!) che ho preso su Amazon, ma gli appassionati di elettronica avranno senz’altro tutto già in casa, o potranno procurarselo per pochi spiccioli dal loro abituale “pusher” di componenti.

Il programma in Python

Un modo molto semplice per realizzare programmi che lavorano con i pin GPIO del Raspberry Pi è utilizzare il linguaggio Python assieme alla libreria RPi.GPIO. Sia Python che RPi.GPIO sono già installati su Raspbian (il sistema operativo di riferimento per il Raspberry Pi) e, quindi, non resta che scrivere il programma.

Questo è lo script Python che fa tutto il lavoro:

import RPi.GPIO as GPIO
import time
import os

def blink(channel, count = 1, timeHigh = 1000, timeLow = 1000):
    """Blink count times, timeHigh milliseconds high, timeLow milliseconds low."""

    if count <= 0:
         return
 
    GPIO.output(channel, GPIO.HIGH)
    time.sleep(timeHigh / 1000.0)
    GPIO.output(channel, GPIO.LOW)
 
    for i in range(count - 1):
        time.sleep(timeLow / 1000.0)
        GPIO.output(channel, GPIO.HIGH)
        time.sleep(timeHigh / 1000.0)
        GPIO.output(channel, GPIO.LOW)

led = 17
button = 18

# Init GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(led, GPIO.OUT)
GPIO.setup(button, GPIO.IN, pull_up_down = GPIO.PUD_UP)

GPIO.wait_for_edge(button, GPIO.FALLING)
channel = GPIO.wait_for_edge(button, GPIO.RISING, timeout = 3000)
if channel is None:
    blink(led, 3, 500, 500)
    print "Shutdown"
    os.system("/sbin/shutdown -h now")
else:
    blink(led, 1, 500)
    print "Reboot"
    os.system("/sbin/shutdown -r now")
 
GPIO.cleanup()

A questo punto occorre aprire una parentesi sulla numerazione dei pin del Raspberry Pi. Ci sono 3 diversi sistemi per numerare, o identificare, i pin GPIO del Raspberry Pi:

  • numerazione “fisica”
  • numerazione “BCM”
  • numerazione “wiringPi”

La numerazione fisica è quella che identifica i pin con la loro posizione sul connettore; la numerazione BCM identifica i pin in base a come sono collegati al SOC Broadcom (ad esempio, il pin 11 del connettore è collegato al pin GPIO17 del chip BCM2835, e quindi avrà identificativo BCM pari a 17); la numerazione wiringPi è quella che viene usata dalla libreria C wiringPi, ed è basata sulla posizione che i bit, corrispondenti a ciascun pin, hanno nelle porte I/O che si programmano direttamente da C e assembly (ad esempio, il pin 11 del connettore ha identificativo 0 nella numerazione wiringPi perché corrisponde al bit 0 della prima porta I/O, la porta A). Uno schema che consente di identificare i pin in ognuno dei tre sistemi può essere reperito qui: http://pinout.xyz/. Quando si scrive un programma, una delle prime scelte da fare è quale tipo di numerazione dei pin si intende utilizzare. Posto che nessuno di questi sistemi è immune da difetti (potrà sempre capitare di dover riscrivere il codice e/o dover ricablare l’impianto nel momento in cui si cambia modello, o anche solo revisione, di Raspberry Pi), io ho scelto la numerazione BCM (GPIO.setmode(GPIO.BCM)): alcuni pin hanno anche delle funzioni alternative imposte dal chip BCM (interfaccia seriale, I2C, SPI etc.) che restano “ancorate” all’identificativo BCM e quindi, con la numerazione BCM, sarà più semplice gestire futuri cambiamenti del pinout anche relativamente alle funzioni alternative.

La pressione del pulsante collega il pin GPIO18 a massa, facendo leggere una tensione di 0 V sul pin stesso (valore logico 0). Quando il pulsante non è premuto, la tensione letta potrebbe essere un valore qualsiasi. È per questo motivo che, sul pin GPIO18, viene abilitato il circuito di pull-up interno al Raspberry Pi (istruzione GPIO.setup(button, GPIO.IN, pull_up_down = GPIO.PUD_UP)): in questo modo si ha la garanzia che il valore di tensione letto con il pulsante non premuto sia 3.3 V (valore logico 1).

La funzione utilizzata per monitorare il pulsante è GPIO.wait_for_edge(). Questa è una funzione bloccante che non consuma risorse di sistema e che rimane in attesa all’infinito sul pin GPIO18, fino a che non viene rilevato un fronte in discesa (1 -> 0): la pressione del pulsante. Successivamente si rimane in attesa, per un periodo massimo di tre secondi, di un fronte in salita (0 -> 1, il rilascio del pulsante) con l’istruzione GPIO.wait_for_edge(button, GPIO.RISING, timeout = 3000). Se si esce perché il fronte in salita è stato rilevato, significa che la pressione del pulsante è durata meno di 3 secondi, e quindi si opta per il reboot; altrimenti si esce perché sono passati 3 secondi senza che il pulsante sia stato rilasciato, e quindi si opta per lo shutdown.

La funzione blink(), definita all’inizio dello script, si occupa di notificare all’utente il riconoscimento della pressione del pulsante: un unico lampeggio nel caso di pressione breve, tre lampeggi nel caso di pressione lunga.

La funzione os.system() serve per lanciare l’esecuzione di un comando utilizzando la shell di sistema. In questo caso i comandi da lanciare sono ‘/sbin/shutdown -h now’ per lo shutdown, o ‘/sbin/shutdown -r now’ per il riavvio.

Per salvare lo script sul Raspberry Pi occorre effettuare il login su di esso (direttamente o via ssh), lanciare l’editor nano con il comando:

$ nano shutPi.py

, inserire il testo dello script (facendo bene attenzione all’indentazione, che in Python serve per identificare i blocchi di codice), e chiudere nano con Ctrl+X (ricordandosi di salvare il file in uscita). A questo punto lo script dovrebbe essere stato salvato sul file col nome /home/pi/shutPi.py.

Ora si può passare al collaudo. Per eseguire lo script si deve dare il comando:

$ sudo python /home/pi/shutPi.py

, dove il comando sudo è necessario perché il comando shutdown, lanciato dallo script, richiede i privilegi di super utente. Alla pressione del pulsante, dopo il lampeggio del LED e un attimo prima dell’arresto del sistema, sul terminale dovrebbe comparire la scritta Shutdown quando viene intercettata una pressione lunga, e la scritta Reboot quando viene intercettata una pressione breve.

L’avvio automatico

Una volta che si è verificato che lo script funziona come si deve, si può impostare il Pi per lanciarlo automaticamente ad ogni avvio. Per fare questo occorre modificare il file /etc/rc.local:

$ sudo nano /etc/rc.local

ed aggiungere, prima della riga ‘exit 0’, le righe che seguono:

if [ -f /home/pi/shutPi.py ]; then
    python /home/pi/shutPi.py &
fi

Qui sudo non serve, perché lo script /etc/rc.local viene eseguito già con i privilegi di super utente; la e commerciale (&) in fondo alla seconda linea serve per lanciare il nostro script in background, in modo da non bloccare l’esecuzione di eventuali altre parti del file /etc/rc.local.

A questo punto si può chiudere con Ctrl+X, avendo cura di salvare il file. D’ora in avanti lo  script shutPi.py verrà lanciato automaticamente, e in background, ad ogni avvio. Per verificarlo, dopo aver riavviato, si può dare il comando:

$ ps ax | grep shutPi.py

Possibili semplificazioni

Se non interessa avere il feedback fornito dal LED, o non ne abbiamo uno a disposizione, la parte relativa al LED può essere tranquillamente stralciata sia dal circuito che dal programma e il pulsante continuerà a funzionare come prima.

Io uso correntemente una versione ulteriormente ridotta del circuito che fa a meno anche del pulsante: del circuito originale ho mantenuto solamente il jumper rosso e uno dei due jumper neri. I due jumper, all’avvio del Raspberry Pi e per tutta la fase di normale funzionamento, non devono essere lasciati liberi di toccarsi. Quando si deve arrestare il sistema, per innescare la procedura di shutdown o reboot sarà sufficiente toccare tra di loro (rispettivamente: almeno 3 secondi per lo shutdown, meno di 3 secondi per il reboot) i capi liberi dei due jumper.

Riferimenti

Annunci

14 Pensieri su &Idquo;Pulsante di shutdown/reboot per Raspbery Pi

  1. Ciao, molto interessante il tuo lavoro!
    Io sto realizzando una mini console con un RaspBerry pi Zero.
    Volevo collegare un pulsante per il shutdown, quando é premuto crea il contatto, quando lo ripresi stacca il contatto.
    In teoria dovrebbe essere un pulsante NA.
    Ah il sistema operativo é Retropie/Emulationstation.
    Puoi aiutarmi in alcuni passaggi che non capisco?
    Grazie.

  2. Ciao, ho utilizzato il tuo programma montando un pulsante ed un led (stessi pin). Funziona tutto /reboot e shutdown), ma spesso il rasp da solo esegue un reboot o uno shutdown…. ho dovuto rimuover eil programma dall’autostart per risolvere. Ho anche cambiato pin. Cosa ne pensi?

    • Ciao Guido!
      È possibile che il tuo pulsante sia difettoso e provochi dei contatti non voluti? In questo caso dovresti vedere anche il led accendersi.
      Saluti,
      A.

  3. ciao, interessante il tuo lavoro. sto cercando di implementare un pulsante per accendere e spegnere il raspberry su cui ho installato volumio. Non riesco però a creare la cartella home/pi perché mi dice che non ho il permesso. ho creato pertanto la cartella pi direttamente fuori la cartella home e ci ho salvato lo script. Ho infine lanciato il comando sudo python /pi/shutPi.py ma non mi riconosce il comando. Perché non riconosce i comandi?

    • Ciao Modesto,
      questo post si basa su Raspbian, che è il sistema operativo più diffuso per Raspberry Pi, ma non l’unico ;-). Ci sono sensibili differenze tra Volumio e Raspbian: Volumio è orientato ad un uso specifico (fare da player audio), mentre Raspbian è un sistema operativo “general purpose”, in grado di far girare un ampio spettro di applicazioni diverse.

      Per quanto riguarda la cartella /home/pi, quella è semplicemente la cartella home dell’utente pi, l’utente predefinito di Raspbian. Volumio non ha, per default, un utente di nome pi, ma un utente di nome volumio. Lo script può essere tranquillamente piazzato in una cartella qualsiasi (/dove/piace/a/te/shutPi.py), quindi anche /pi/shutPi.py va bene: l’importante è essere coerenti con la posizione scelta.

      Per il secondo problema, non so se su Volumio è presente tutto il necessario: il comando sudo, l’interprete Python e il modulo Python RPi.GPIO. In modo da capire cosa manca, puoi riportare esattamente il messaggio di errore che ti viene restituito?

      Ciao e grazie per l’interessamento!

  4. alla fine il file l’ho salvato nella directory /home/volumio/shutPi.py

    ecco l’errore che mi da

    volumio@volumio:~$ sudo python /home/volumio/shutPi.py
    Traceback (most recent call last):
    File “/home/volumio/shutPi.py”, line 1, in
    import RPi.GPIO as GPIO
    ImportError: No module named RPi.GPIO

    • A quanto pare manca il modulo Python RPi.GPIO. Non so, però, se si può – e come – installarlo su Volumio… io proverei a chiedere sul forum di Volumio.
      Ciao

  5. Ciao ho replicato il tuo progetto ma qualcosa non va. Alla pressione del pulsante il codice salta sempre direttamente all’ else eseguendo quindi un reboot indipendentemente dal tempo di pressione del pulsante. La funzione GPIO.wait_for_edge() non attende il fronte di salita. Hai suggerimenti?
    Grazie

    • Non saprei… prova a fare un po’ di debug commentando le linee con il comando shutdown e facendo stampare a terminale il valore della variabile channel.
      Ciao!

  6. ciao io vorrei utilizzare questo script su raspberry con installato retropie. ho però tutte le porte gpio impegnate per i tasti e joystick. ti chiedo se possibile usare il pin gpio2 o 3 o 0 o 1.
    ho fatto una prova a collegarlo al 2 ma mi dice : /home/pi/shutPi.py:26: RuntimeWarning: This channel is already in use, continuing anyway. Use GPIO.setwarnings(False) to disable warnings.

    non ho collegato il led.

    inoltre ti chiedo come mai se utilizzo il gpio 18 come descritto, alla pressione del tasto il sistema mi fa un reboot e non legge la pressione superiore ai 3 sec per lo shoutdown.

    • Ciao Giuseppe,
      ti consiglio di usare solamente i pin segnalati dal colore verde nello schema che trovi su https://it.pinout.xyz/pinout/pin3_gpio2 (facendo click sul nome del pin stesso ottieni anche una descrizione dell’uso nella configurazione di default). Gli altri pin possono avere alcuni collegamenti particolari (ad esempio una resistenza di pull-up) oppure avere una configurazione per scopi specifici (I2C o altro).

      Quanto al problema dei 3 secondi, sei già la seconda persona, dopo Marco, che lamenta un problema simile. Cercherei di fare un po’ di debug della funzione GPIO.wait_for_edge() avendo commentato le due linee con i comandi di shutdown (in modo da non provocare lo shutdown per davvero). Dopo quanto tempo ritorna? Che valore ritorna. Prova anche a testare il valore letto dalla funzione GPIO.input(), magari ciclicamente, per vedere se il valore del pulsante viene letto correttamente. Qui (https://sourceforge.net/p/raspberry-gpio-python/wiki/Inputs/) puoi trovare alcuni esempi d’uso per queste funzioni.

      • Restituisce il valore reboot nonostante i 3sec premuto.

        Ho trovato questo script:

        #!/usr/bin/python

        # -*- coding: utf-8 -*-

        import RPi.GPIO as GPIO

        import os

        GPIO.setmode(GPIO.BCM)

        GPIO.setwarnings(False)

        #Nelle due righe seguenti, eventualmente sostituire 10 col numero del GPIO a cui avete collegato il pulsante (numerazione GPIO.BCM)

        GPIO.setup(10,GPIO.IN,pull_up_down=GPIO.PUD_UP)

        GPIO.wait_for_edge(10,GPIO.FALLING)

        os.system(“poweroff”)

        Che configurato sul pin gpio 3 e gnd prendendolo mi effettua lo spegnimento del raspi. Se volessi eseguirlo ma dandogli i 3 sec è fattibile secondo te? Il tuo è molto comodo perché con un tasto si hanno 2 funzioni. Sarebbe bello se non fosse problemi.
        Grazie cmq

      • Con valore restituito intendevo dire non la visualizzazione della scritta “Reboot”, che era ovvio aspettarsi visto che il Raspberry, effettivamente fa il reboot. Ma stampare il valore della variabile channel, come restituito dalla funzione GPIO.wai_for_edge(): lo shutdown avviene solamente se channel vale None, il reboot in tutti gli altri casi possibili, quindi può essere interessante vedere quanto vale, effettivamente, channel. Magari nella tua particolare configurazione potrebbe interferire in quel momento la condizione di un altro pulsante. E anche, poi, stampare a video il valore di ritorno della prima invocazione di GPIO.wait_for_edge() (quella che scatta al momento della pressione).
        Una cosa del genere, insomma:
        channel = GPIO.wait_for_edge(button, GPIO.FALLING)
        print channel
        channel = GPIO.wait_for_edge(button, GPIO.RISING, timeout = 3000)
        print channel

        Nel caso di funzionamento corretto, in etrambe le stampe channel deve valere esattamente come button.
        Un’altra domanda: quanto tempo passa dalla pressione del pulsante alla stampa? È istantaneo o passa qualche tempo? Infine, puoi escludere che non si tratti di un problema del pulsante che stai usando? Sei sicuro che il pulsante riesca a chiudere il contatto per tutto il tempo in cui lo tieni premuto?
        Come potrai capire fare debug “a distanza” è molto complicato…

        Sulla domanda che hai fatto: se metti, ad esempio GPIO.wait_for_edge(10,GPIO.FALLING, timeout=3000), la funzione aspetterebbe per la pressione del pulsante solamente per 3 secondi, poi si esegue incondizionatamente lo shutdown. In sostanza, appena 3 secondi dopo l’avvio il Rapsberry si spegnerebbe automaticamente. In quello script l’attesa deve poter andare avanti all’infinito, quindi non ci vuole nessun timeout.

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

w

Connessione a %s...

This site uses Akismet to reduce spam. Learn how your comment data is processed.