14.3.2. O watchdog¶
O watchdog de hardware é a base sobre a qual assenta todas as outras escolhas de proteção. É um pequeno temporizador independente que reinicia o processador quando não foi informado em contrário por demasiado tempo. Um script que bloqueia num sensor instável, uma chamada de rede que bloqueia após o seu timeout, um alocador de memória preso num canto do heap, uma exceção que escapou ao loop – nenhum deles para o watchdog. O temporizador conta decrescentemente independentemente, e a câmara reinicia.
Para um produto enviado, um watchdog não é opcional. Sem ele, qualquer um dos modos de falha acima deixa a câmara parada até que alguém repare e a ligue de novo. Com ele, a câmara recupera por si própria e a única evidência da falha é uma linha no registo.
Veja também
A página do temporizador watchdog no capítulo de hardware aborda o que é um watchdog ao nível do hardware e as noções básicas da API machine.WDT. Esta página aborda o que muda numa implementação em produção.
14.3.2.1. Iniciar o watchdog¶
machine.WDT é a API. É suportada por hardware: uma vez construído, o temporizador corre até ao próximo reinício. Não existe stop(), nem deinit(), nem escape por Ctrl-C. Esse é o objetivo.
Uma configuração típica no início de main.py, imediatamente antes do loop que protege:
from machine import WDT
wdt = WDT(timeout=10_000) # milliseconds
main.py é o local correto para o watchdog porque é aí que o loop reside. Um reinício por watchdog é um reinício de hardware, pelo que o caminho de arranque a frio é re-executado e main.py re-entra no loop autonomamente – a recuperação funciona sem qualquer ligação em boot.py. Iniciar o watchdog em boot.py significa que cada reinício suave (um Ctrl-D de um programador, por exemplo) entrega à aplicação um temporizador de hardware que não tem forma de parar, o que é um incómodo no banco de trabalho e uma armadilha no código de configuração de produção que corre antes de o loop estar pronto.
Escolha o timeout para ser 2 a 3 vezes mais longo do que o pior tempo de iteração observado do loop principal. Jitter na taxa de fotogramas, uma leitura lenta de sensor num sensor frio, uma breve falha de Wi-Fi – nenhum destes deve disparar o watchdog. Um bloqueio real (um loop infinito, uma chamada de E/S bloqueada) deve. Timeouts demasiado curtos tornam o watchdog numa fonte de reinícios falsos; timeouts demasiado longos deixam a câmara sem resposta durante minutos antes de a recuperação disparar.
14.3.2.2. Alimentá-lo¶
wdt.feed() reinicia a contagem decrescente. Chame-o uma vez por iteração do loop principal, no início do corpo do loop para que a alimentação ocorra incondicionalmente antes de qualquer trabalho que possa bloquear:
while True:
wdt.feed()
frame = csi0.snapshot()
process(frame)
14.3.2.3. Sobreviver a exceções¶
O watchdog trata dos bloqueios. As exceções são um modo de falha diferente. Uma exceção não tratada sobe ao nível superior do script, main.py termina e a câmara cai para o REPL. O watchdog dispara então após o seu timeout porque nada o está a alimentar a partir do REPL, a câmara reinicia e main.py corre novamente – portanto a recuperação funciona, mas o campo paga um timeout completo mais reinício por cada falha, o traceback vai para o stdout USB que nada lê e qualquer estado em memória que a aplicação estava a manter desaparece.
Envolver o loop principal numa instrução try / except de nível superior transforma uma falha num evento registado pelo qual a aplicação continua, sem pagar o custo de um reinício:
import logging
log = logging.getLogger(__name__)
while True:
wdt.feed()
try:
frame = csi0.snapshot()
process(frame)
except Exception:
log.exception("frame loop iteration failed")
Capturar Exception (não BaseException) mantém KeyboardInterrupt e SystemExit a funcionar, que é o que um programador ligado por USB pretende.
Este padrão é a metade de software da vivacidade: o watchdog captura os bloqueios, o invólucro captura as falhas e o registo regista o que qualquer um deles capturou.
14.3.2.4. Saber por que aconteceu um arranque¶
Cada reinício suave e cada reinício por watchdog acabam por aparecer como um arranque a frio. O auxiliar de diagnóstico no arranque regista machine.reset_cause() em cada arranque a frio; a linha reset cause é o que indica ao campo se a recuperação realmente disparou em comparação com a câmara a ciclar normalmente.
A linha da causa do reinício é o que torna o trabalho do watchdog visível no registo. Um registo cheio de reinícios watchdog timeout diz que a aplicação tem estado a bloquear e o watchdog tem estado a recuperá-la. Um registo sem eles diz que o watchdog não teve de disparar – o que normalmente significa que a aplicação está saudável, mas pode também significar que o timeout está demasiado longo para capturar os bloqueios que estão realmente a acontecer.
14.3.2.5. Um arranque completo¶
Um main.py que reúne watchdog, configuração de registo, diagnóstico no arranque e o invólucro tem este aspeto:
import logging
from machine import WDT
from app.logging_setup import setup_logging, log_boot_diagnostics
setup_logging('/sdcard/logs/app.log')
log_boot_diagnostics()
log = logging.getLogger(__name__)
wdt = WDT(timeout=10_000)
while True:
wdt.feed()
try:
step()
except Exception:
log.exception("loop iteration failed")
step() é o trabalho por iteração da aplicação; o resto deste andaime não muda entre produtos. A proteção é um watchdog, um invólucro e um arranque registado em cada arranque a frio – não muito código, e a diferença entre uma câmara que recupera autonomamente e uma que precisa de uma chamada de serviço.