Optimiser une boucle `while`

8

J'ai créé un mini script pour redémarrer mon Raspberry Pi sur simple pression d'un bouton. Le script utilise simplement le câblagePi (commande gpio) pour définir la broche 0 (broche 17 dans l'ordre de numérotation standard du Raspberry Pi) pour entrer, puis lit la valeur jusqu'à ce qu'elle soit une (c'est-à-dire lorsque le bouton est enfoncé ou maintenu enfoncé).

Voici mon script:

gpio mode 0 in

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

Le script fonctionne bien et tout.

Cependant, pour ceux d'entre vous qui ne connaissent pas le Pi, il est livré avec des ressources matérielles très limitées (dont 512 Mo de mémoire) qui peuvent être facilement consommées par une boucle comme celle que j'utilise.

Ce que j'essaie de réaliser ici, c'est de trouver une autre façon pour bash de savoir quand la valeur est passée de 0à 1sans avoir à lui consacrer davantage une boucle inconditionnelle. Est-ce faisable? Veuillez partager vos idées.

Fadi Hanna AL-Kass
la source
3
Avez-vous envisagé d'utiliser des interruptions pour gérer un événement matériel ou est-ce absolument impossible dans votre cas? adafruit.com/blog/2013/03/29/…
lgeorget
3
Je voudrais simplement ajouter un appel de sommeil qui devrait limiter la consommation de mémoire
strugee
Les interruptions @lgeorget seraient idéales, mais probablement pas gérées depuis bash.
jordanm
Cette boucle ne consomme pas de mémoire.
OrangeDog

Réponses:

11

Analyse et solution moderne

Le script est une boucle occupée: il continue de lire les broches GPIO encore et encore. Il ne consomme pas beaucoup de mémoire mais garde le CPU occupé.

Vous devez définir la broche GPIO en mode bord. L' gpioutilitaire dispose d'une commande wfi(attendre l'interruption) que vous pouvez utiliser pour réagir à un déclencheur de front. ( gpio wfin'existait pas lorsque la question a été posée.)

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

Une solution Python

Il existe une bibliothèque Python pour l'accès GPIO , qui prend en charge le mode Edge. Voici du code Python complètement non testé qui devrait faire ce que vous voulez.

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

Conseils supplémentaires sur la coque

(true)pourrait être écrit juste true. Les parenthèses créent un sous-processus, ce qui est complètement inutile.

`gpio read 0`devrait être entre guillemets. Sans guillemets, la sortie de la commande est traitée comme une liste de modèles génériques de nom de fichier. Avec des guillemets doubles, la sortie de la commande est traitée comme une chaîne. Mettez toujours des guillemets autour des substitutions de commandes et des substitutions de variables: "$(some_command)", "$some_variable". En outre, vous devez utiliser la syntaxe $(…)plutôt que `…`: elle a exactement la même signification, mais la syntaxe backquote a quelques bizarreries d'analyse lorsque la commande est complexe. Donc:if [ "$(gpio read 0)" -eq 1 ]

Ne mettez pas le mot de passe root dans le script. Si le script s'exécute en tant que root, vous n'avez pas du tout besoin de sudo. Si le script ne s'exécute pas en tant que root, accordez à l'utilisateur exécutant le script l'autorisation de s'exécuter sudo rebootsans fournir de mot de passe. Exécutez visudoet ajoutez la ligne suivante:

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

Notez que s'il y a une entrée pour le même utilisateur dans le fichier sudoers qui nécessite un mot de passe, l' NOPASSWDentrée doit venir après.

Une fois que vous avez déclenché un redémarrage, vous n'avez pas besoin de rompre la boucle, le système s'arrêtera quand même.

Si vous décidez de continuer à utiliser ce script shell et que votre version de gpioest trop ancienne pour avoir la wfisous - commande, voici une version améliorée qui ne vérifie que l'état du bouton toutes les secondes. Notez que comme la broche n'est lue qu'une fois par seconde, cela signifie que vous devez maintenir le bouton enfoncé pendant au moins une seconde pour être sûr que l'événement est détecté.

gpio mode 0 in
while sleep 1; do
    if [ "$(gpio read 0)" -eq 1 ]; then
        reboot
    fi
done &
Gilles 'SO- arrête d'être méchant'
la source
1
Pour votre dernier exemple, vous pourriez dormir pendant une fraction de seconde . Quelque chose comme 0.1ou peut-être 0.2devrait être capable de détecter des pressions très courtes et de laisser suffisamment de temps CPU pour les autres threads.
Bob
@Bob: Bien que la portabilité n'ait probablement pas d'importance dans ce cas, sleep(1)l'acceptation d'un nombre fractionnaire de secondes n'est pas standard.
1
Mise à jour: il existe une telle commande d'attente: gpio wfi 0 risingattendrait un front montant sur la broche zéro, qui n'est pas occupée (selon le site de câblage pi ).
CodenameLambda
3

Ce que vous avez est connu comme une boucle occupée . Votre boucle ne consommera pratiquement pas de mémoire, mais elle consommera beaucoup de CPU. Ceci est généralement atténué en ajoutant sleepau corps de la boucle.

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

Se débarrasser de la boucle occupée dépendra de ce qui se gpiopasse. Il existe des appels système tels que select(), qui peuvent bloquer jusqu'à ce qu'un descripteur de fichier soit prêt.

En ce qui concerne l'efficacité, la commande ()around trues'exécute trueen fait dans un sous-shell. Cela n'est pas nécessaire et peut être mieux exprimé par ce qui suit:

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

Essayez ce qui suit:

while ! gpio read 0 ; do
    sleep 1
done
echo password | sudo -S reboot
client
la source