Shutdown pulito per mancanza corrente

Ieri notte è mancata la corrente per 5 ore e mezza. Decisamente troppo anche per dei buoni gruppi di continuità. Purtroppo alcuni sistemi possono danneggiarsi quando si toglie di botto l’alimentazione. I gruppi di continuità fanno quello che possono, ma in un “Paese Civile” non ci si aspetta che, in piena notte, tutti i sistemi di un’intera zona industriale possano rimanere senza corrente per 5 ore e mezza. E’, come dire, disdicevole.

Avevo un vecchio progettino in garage e sono andato a riprenderlo. E’ molto semplice. Normalmente, sui gruppi di continuità, c’è già un sistema in grado di trasmettere (in qualche modo) la condizione di power-fail, ma spesso i drivers per intercettare questo avviso sono farraginosi e qualche volta, durante l’installazione, si rischia di bloccare l’intero sistema. Allora io, che amo le cose semplici, ho deciso di farmi un circuito che mi avvisi e che sia facile da gestire, con un programmino di controllo scritto da me medesimo.

Vediamo l’hardware: è composto di due sezioni. Questa è la prima

Occhio a non metterci le dita! C’è la tensione a 220V che vi può friggere!

E qui c’è la realizzazione pratica:

Si tratta di un circuito molto semplice. In pratica, si accendono due led in modo “transformerless” grazie alla reattanza di un condensatore, in una configurazione ben nota. I led coinvolti sono quello nell’optocoupler, che serve per lo stadio successivo, e l’altro visibile che serve sia per proteggere il precedente da tensioni inverse, sia come “monitor” per visualizzare la presenza della tensione di rete.

Ora passiamo alla seconda metà del circuito.

In pratica, se l’optocoupler è acceso (c’è corrente) il transistor si chiude e quel che accade è che qualsiasi segnale trasmesso sulla porta seriale viene immediatamente ricevuto (tx ed rx sono praticamente collegati tra loro). Se il transistor non conduce, è segno di mancanza di rete e il led è spento e quindi non avremo più l’eco dei caratteri trasmessi. Ecco la seconda parte del circuito, con una interfaccia usb-seriale “very low cost” collegata:

Molto semplice. Con qualsiasi computer, se si riesce a controllare la porta seriale (non mi è mai capitato di avere difficoltà) si può scrivere un programmino che invii un pacchetto di dati sulla usb-serial e verifichi che questi siano subito ricevuti. Se lo sono, la tensione di rete c’è. Se non lo sono, siamo in power-down e dobbiamo fare qualcosa. Possiamo mandare una email all’amministratore e quindi fare uno shutdown pulito, oppure possiamo aspettare un po’ per vedere se la tensione ritorna, ma tutto sarà veramente facile. Si tratterà solo di scrivere un banale programmino di controllo e i drivers da aggiungere al sistema saranno quelli idonei per l’interfaccia seriale che useremo.

Nel caso in cui il nostro sistema disponga di pin di I/O (tipo una Raspberry PI), allora potremo cambiare completamente filosofia e usare il circuito che segue:

In questo caso, la mancanza di corrente porterà un livello basso sul collettore del transistor e quindi sarà facile scrivere un programmino che verifichi lo stato della linea e decida di conseguenza le operazioni da fare. Si noti il pulsante, che permette di fare uno shutdown pulito anche manualmente.

Ecco un’immagine del circuito montato su una scheda per prototipi:

Questo è cio che vediamo sul morsetto d’ingresso quando il circuito a 220V è alimentato:

Ho scritto un piccolo programmino in Python per realizzare quanto voluto, cioè spegnere la Raspberry PI se la tensione di rete (a 220V) manca per un minuto. Questo è il listato (molto minimalista)

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

cnt = 0  # reset counter for pushbutton pressed
GPIO.setmode(GPIO.BCM)  # set GPIO mode
while True:  # main loop
  GPIO.setup(4, GPIO.OUT)  # GPIO.4 set as output
  GPIO.output(4, False)  # GPIO.4 output set at low level (led ON)
  time.sleep(0.2)  # 200mS pause with led ON
  GPIO.setup(4, GPIO.IN, pull_up_down = GPIO.PUD_OFF)  # GPIO.4 is input, no pull-up
  for n in range(0,2):  # exec twice
    time.sleep(0.1)  # wait for 100 mS prior to read GPIO.4 status
    if GPIO.input(4):  # if GPIO.4 is high (no power-fail, no button pressed)
      cnt = 0  # reset counter
    else:  # if GPIO.4 is low (power-fail or button pressed)
      cnt += 1  # increment counter
  if cnt >= 120:  # if continuously LOW for 60 seconds (cnt increments 2 times in a second)
    call("sudo poweroff", shell=True)  # terminate and shutdown
    break
  time.sleep(0.6)  # if not poweroff, wait the remaining time for 1 Sec main cycle

Questo file viene salvato come ‘service.py’ nella cartella /home/pi. Per avviarlo automaticamente al boot, dovremo dare da terminale il comando ‘sudo crontab -e’ e aggiungere alla fine del file la linea:

@reboot sudo python /home/pi/service.py &

Come si vede dal listato, ho usato la linea GPIO4 e ho aggiunto anche un led rosso, per fare da monitor del funzionamento del programma. Il led si accenderà lampeggiando se la tensione di rete è presente, altrimenti diventerà a luce fissa e dopo un minuto -continuativo- di mancanza di tensione, verrà eseguito lo shutdown. ATTENZIONE: se abilitate il programma di controllo e non avete l’hardware collegato, la Raspberry si spegnerà un minuto dopo l’avvio, perché leggerà (in assenza di circuito) un livello basso sul pin di test. Quindi, per fare le prove software con tranquillità, mettete una resistenza da 10K tra il pin GPIO4 e il +3.3V, come hardware di simulazione minimale.

Questo è lo schema modificato con il led (il pulsante si può mettere oppure no, a scelta):

Naturalmente, per eseguire lo shutdown dopo un minuto dalla mancanza di tensione, è necessario che la Raspberry PI sia alimentata da un sistema con batteria! Vi invito a leggere un mio vecchio articolo (in Inglese, ma c’è il traduttore a fondo pagina) che spiega come realizzare questa cosa con relativa semplicità. Trovate l’articolo a questo link:

Single 3.7V Li-ion cell battery back-up for Raspberry Pi

Ecco una vista d’insieme del circuito di test in fase di collaudo:

Notate il piccolo connettore montato sui primi pin della Raspberry PI. I segnali utilizzati sono +3.3V, +5V, GND e GPIO4

Mentre il circuito del monitor di rete è PERICOLOSO e non si può toccare (deve essere ben chiuso e isolato) la parte dopo il fotoaccoppiatore non è problematica. Siamo isolati dalla tensione di rete e quindi non ci sono problemi. Io ho messo la parte “pericolosa” all’interno di una lucina notturna comprata per un euro in un negozio (poi smontata e adattata allo scopo) e ho portato fuori con un cavo la sezione isolata dell’optocoupler.

Ora, veniamo all’applicazione che usa l’eco sulla seriale, per dispositivi (tipo notebook) in cui non possiamo usare un pin di I/O per verificare lo stato di un segnale. Ho scritto un programmino usando Gambas2 (una vecchia versione) che gira sul mio vecchissimo notebook con S.O. Puppy Linux 4.3.1 e qui c’è il listato:

' Gambas module file
'-------------------
' last update: 2021/07/25
'
PUBLIC gbComm1 AS NEW SerialPort

PUBLIC SUB Main()
DIM rxd AS String = "" ' rx buffer for COM port
DIM cnt AS Integer = 0 ' num of rx errors

  PRINT ": Service started: "; Now()
  TRY CLOSE gbComm1 ' try close the com if open
  gbComm1.PortName = "/dev/ttyUSB0" ' com name default - change if needed
  gbComm1.FlowControl = 0
  gbComm1.DataBits = 8
  gbComm1.StopBits = 1
  gbComm1.Parity = 0
  gbComm1.Speed = 38400
  PRINT "> Open Com port: " & gbComm1.PortName & " speed: " & gbComm1.Speed 
  TRY gbComm1.OPEN() ' open comm port 
  IF ERROR THEN ' if port error 
    PRINT "#ERR: Com port not available" 
    QUIT ' terminate for unrecoverable error
  ENDIF 
  WHILE 1 ' loop forever (ctrl-c to quit)
    READ #gbComm1, rxd, Lof(gbComm1) ' flush rx queue
    PRINT #gbComm1, "AT" ' send AT to com port
    PRINT "tx: AT"
    WAIT 1 ' delay 1000 mS 
    READ #gbComm1, rxd, Lof(gbComm1) ' read echo
    IF InStr(rxd, "AT") > 0 THEN ' echo received 
      cnt = 0 ' reset error counter
      PRINT "rx: AT" ' console print rx data
    ELSE ' no echo (power-fail)
      cnt = cnt + 1
      PRINT "rx=["; rxd; "] cnt="; cnt ' console print rxd and error counter
      IF cnt >= 60 THEN ' if one minute without echo
        SHELL "wmpoweroff" ' shutdown command in Puppy Linux
      ENDIF 
    ENDIF     
  WEND 
  TRY CLOSE gbComm1 ' close com
  PRINT ": Service terminated: "; Now() ' service closed
END

Si tratta di una versione elementare, giusto per dare un’idea. Come nella versione in Python, anche qui, se manca alimentazione di rete per un minuto, il sistema va in shutdown. In questo caso, trattandosi di un notebook, l’alimentazione è garantita dalla batteria interna, per il tempo necessario.

Con questo concludo. Al prossimo “progetto del weekend” ! 🙂