Optimizar un bucle `while`

8

He creado un mini script para reiniciar mi Raspberry Pi con solo presionar un botón. La secuencia de comandos simplemente usa cableadoPi (comando gpio) para establecer el pin 0 (pin 17 en el orden de numeración estándar de Raspberry Pi) para ingresar, y luego lee el valor hasta que sea uno (es decir, cuando se presiona o mantiene presionado el botón).

Aquí está mi guión:

gpio mode 0 in

while (true)
do
        if [ `gpio read 0` -eq 1 ]
        then
                echo password | sudo -S reboot
                break
        fi
done &

El guión funciona bien y todo.

Sin embargo, para aquellos de ustedes que no están familiarizados con el Pi, viene con recursos de hardware muy limitados (que incluyen 512 MB de memoria) que pueden ser fácilmente consumidos por un bucle como el que estoy usando.

Lo que estoy tratando de lograr aquí es encontrar otra forma para que bash descubra cuándo el valor ha cambiado de 0a 1sin tener que dedicarle un ciclo más incondicional. ¿Es esto factible? Por favor comparte tus ideas.

Fadi Hanna AL-Kass
fuente
3
¿Ha considerado usar interrupciones para manejar eventos de hardware o es absolutamente imposible en su caso? adafruit.com/blog/2013/03/29/…
lgeorget
3
Sólo quiero añadir una llamada del sueño que debe restringir el consumo de memoria
strugee
Las interrupciones de @lgeorget serían ideales, aunque probablemente no se manejen desde bash.
jordanm
Este bucle no consume memoria.
OrangeDog

Respuestas:

11

Análisis y solución moderna.

El script es un ciclo ocupado: sigue leyendo los pines GPIO una y otra vez. No consume mucha memoria pero mantiene ocupada la CPU.

Debe configurar el pin GPIO en modo borde. La gpioutilidad tiene un wficomando (esperar interrupción) que puede usar para reaccionar a un disparador de borde. ( gpio wfino existía cuando se hizo la pregunta).

set -e
gpio mode 0 in
gpio wfi 0 rising
echo password | sudo -S reboot

Una solución Python

Hay una biblioteca de Python para acceso GPIO , que admite el modo de borde. Aquí hay un código de Python completamente no probado que debería hacer lo que quieras.

#!/usr/bin/env python
import os
from RPi import GPIO
GPIO.wait_for_edge(0, GPIO.RISING)
system("sudo reboot")

Consejos de carcasa adicionales

(true)podría escribirse solo true. Los paréntesis crean un subproceso, que es completamente innecesario.

`gpio read 0`debe estar entre comillas dobles. Sin comillas, la salida del comando se trata como una lista de patrones comodín de nombre de archivo. Con comillas dobles, la salida del comando se trata como una cadena. Siempre ponga comillas dobles alrededor de las sustituciones de comandos y las sustituciones de variables: "$(some_command)", "$some_variable". Además, debe usar la sintaxis en $(…)lugar de `…`: tiene exactamente el mismo significado, pero la sintaxis de la comilla inversa tiene algunas peculiaridades de análisis cuando el comando es complejo. Así:if [ "$(gpio read 0)" -eq 1 ]

No ponga la contraseña de root en el script. Si el script se ejecuta como root, no necesita sudo en absoluto. Si el script no se está ejecutando como root, entonces otorgue al usuario que ejecuta el script el permiso para ejecutarse sudo rebootsin proporcionar una contraseña. Ejecute visudoy agregue la siguiente línea:

userwhorunsthescript ALL = (root) NOPASSWD: /sbin/reboot ""

Tenga en cuenta que si hay una entrada para el mismo usuario en el archivo sudoers que requiere una contraseña, la NOPASSWDentrada debe aparecer después.

Una vez que activa un reinicio, no necesita romper el ciclo, el sistema se detendrá de todos modos.

Si decide seguir usando este script de shell y su versión de gpioes demasiado antigua para tener el wfisubcomando, aquí hay una versión mejorada que solo comprueba el estado del botón cada segundo. Tenga en cuenta que dado que el pin solo se lee una vez por segundo, esto significa que debe mantener el botón presionado durante al menos un segundo para asegurarse de que el evento se detecte.

gpio mode 0 in
while sleep 1; do
    if [ "$(gpio read 0)" -eq 1 ]; then
        reboot
    fi
done &
Gilles 'SO- deja de ser malvado'
fuente
1
Para su último ejemplo, podría dormir por una fracción de segundo . Algo así 0.1o tal vez 0.2debería ser capaz de detectar prensas muy cortas y aún así dejar suficiente tiempo de CPU para otros hilos.
Bob
@Bob: Si bien la portabilidad probablemente no importa en este caso, sleep(1)la aceptación de un número fraccionario de segundos no es estándar.
1
Actualización: existe un comando de espera: gpio wfi 0 risingesperaría un flanco ascendente en el pin cero, que no está ocupado (según el sitio de cableado pi ).
CodenameLambda
3

Lo que tienes se conoce como un circuito ocupado . Su bucle no consumirá apenas memoria, pero consumirá mucha CPU. Esto normalmente se mitiga agregando sleepal cuerpo del bucle.

while (true)
do
        if [ `gpio read 0` -eq 1 ]
        then
                echo passowrd | sudo -S reboot
                break
        fi
        sleep 1
done &

Deshacerse del ciclo ocupado dependerá de lo que gpiohaga. Hay llamadas del sistema como select(), que pueden bloquearse hasta que un descriptor de archivo esté listo.

En cuanto a la eficiencia, ()el truecomando alrededor del comando realmente se ejecuta trueen una subshell. Esto no es necesario y se puede expresar mejor con lo siguiente:

while (( $(gpio read 0) != 1 )); do
    sleep 1
done
echo passowrd | sudo -S reboot
jordanm
fuente
-1

Intenta lo siguiente:

while ! gpio read 0 ; do
    sleep 1
done
echo password | sudo -S reboot
invitado
fuente