Otimizando um loop `while`

8

Eu criei um mini script para reiniciar o meu Raspberry Pi com o pressionar de um botão. O script simplesmente usa o wirePi (comando gpio) para definir o pino 0 (pino 17 na ordem de numeração padrão do Raspberry Pi) para inserir e, em seguida, lê o valor até que seja um (ou seja, quando o botão é pressionado ou pressionado).

Aqui está o meu script:

gpio mode 0 in

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

O script funciona bem e tudo.

No entanto, para aqueles que não conhecem o Pi, ele possui recursos de hardware muito limitados (incluindo 512 MB de memória) que podem ser facilmente consumidos por um loop como o que estou usando.

O que estou tentando conseguir aqui é encontrar outra maneira de o bash descobrir quando o valor mudou de 0para 1sem ter que dedicar um loop mais como um incondicional. Isso é factível? Por favor, compartilhe suas idéias.

Fadi Hanna AL-Kass
fonte
3
Você já pensou em usar interrupções para lidar com eventos de hardware ou é absolutamente impossível no seu caso? adafruit.com/blog/2013/03/29/…
lgeorget 17/07/2013
3
Gostaria apenas de acrescentar uma chamada de sono que devem restringir o consumo de memória
strugee
As interrupções do @lgeorget seriam ideais, embora provavelmente não sejam tratadas a partir do bash.
Jordanm
Este loop não consome memória.
OrangeDog

Respostas:

11

Análise e solução moderna

O script é um loop ocupado: ele continua lendo os pinos do GPIO repetidamente. Não consome muita memória, mas mantém a CPU ocupada.

Você deve definir o pino GPIO no modo de borda. O gpioutilitário possui um wficomando (aguarde interrupção) que você pode usar para reagir a um gatilho de borda. ( gpio wfinão existia quando a pergunta foi feita.)

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

Uma solução Python

Há uma biblioteca Python para acesso ao GPIO , que suporta o modo de borda. Aqui está um código Python completamente não testado que deve fazer o que você deseja.

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

Dicas adicionais de shell

(true)poderia ser escrito apenas true. Os parênteses criam um subprocesso, que é completamente desnecessário.

`gpio read 0`deve estar entre aspas duplas. Sem aspas, a saída do comando é tratada como uma lista de padrões curinga do nome do arquivo. Com aspas duplas, a saída do comando é tratada como uma sequência. Sempre coloque aspas duplas em torno das substituições de comandos e substituições de variáveis: "$(some_command)", "$some_variable". Além disso, você deve usar a sintaxe em $(…)vez de `…`: ela tem exatamente o mesmo significado, mas a sintaxe de aspas retroativas possui algumas peculiaridades de análise quando o comando é complexo. Portanto:if [ "$(gpio read 0)" -eq 1 ]

Não coloque a senha root no script. Se o script estiver sendo executado como root, você não precisará do sudo. Se o script não estiver sendo executado como root, dê ao usuário que está executando o script a permissão para executar sudo rebootsem fornecer uma senha. Execute visudoe adicione a seguinte linha:

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

Observe que, se houver uma entrada para o mesmo usuário no arquivo sudoers que exija uma senha, a NOPASSWDentrada deverá ser posterior.

Depois de iniciar uma reinicialização, você não precisa interromper o ciclo, o sistema será interrompido de qualquer maneira.

Se você decidir continuar usando esse script de shell, e sua versão gpiofor muito antiga para ter o wfisubcomando, aqui está uma versão aprimorada que verifica apenas o estado do botão a cada segundo. Observe que, como o pino é lido apenas uma vez por segundo, isso significa que você deve manter o botão pressionado por pelo menos um segundo para garantir que o evento seja selecionado.

gpio mode 0 in
while sleep 1; do
    if [ "$(gpio read 0)" -eq 1 ]; then
        reboot
    fi
done &
Gilles 'SO- parar de ser mau'
fonte
1
Para o seu último exemplo, você pode dormir por uma fração de segundo . Algo como 0.1ou talvez 0.2seja capaz de detectar pressões muito curtas e ainda deixar bastante tempo de CPU para outros threads.
Bob
@ Bob: Embora a portabilidade provavelmente não importe neste caso, sleep(1)a aceitação de um número fracionário de segundos não é padrão.
1
Atualização: Existe um comando de espera: gpio wfi 0 risingesperaria uma borda ascendente no pino zero, que não está ocupada (de acordo com o site de fiação pi ).
CodenameLambda
3

O que você tem é conhecido como um loop ocupado . Seu loop não consumirá quase nenhuma memória, mas consumirá bastante CPU. Isso geralmente é atenuado pela adição sleepao corpo do loop.

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

Livrar-se do loop ocupado depende do que gpiofaz. Existem chamadas de sistema como select(), que podem bloquear até que um descritor de arquivo esteja pronto.

Quanto à eficiência, ()o truecomando around é executado trueem um subshell. Isso não é necessário e pode ser melhor expresso com o seguinte:

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

Tente o seguinte:

while ! gpio read 0 ; do
    sleep 1
done
echo password | sudo -S reboot
hóspede
fonte