14.3.2. O watchdog

O watchdog de hardware é a base sobre a qual toda outra escolha de hardening se apoia. É um pequeno timer independente que reinicia o processador quando não recebe instrução em contrário por tempo demais. Um script que trava em um sensor instável, uma chamada de rede que bloqueia além do seu timeout, um alocador de memória preso em um canto da heap, uma exceção que escapou do laço – nenhum deles para o watchdog. O timer continua a contagem regressiva de qualquer forma, e a cam reinicializa.

Para um produto pronto para envio, um watchdog não é opcional. Sem ele, qualquer um dos modos de falha acima deixa a cam morta até que alguém perceba e desligue e religue a energia. Com ele, a cam volta a funcionar sozinha e a única evidência da falha é uma linha no log.

Ver também

A página timer watchdog do capítulo de hardware cobre o que é um watchdog em nível de hardware e o básico da API machine.WDT. Esta página cobre o que muda em uma implantação de produção.

14.3.2.1. Iniciando o watchdog

machine.WDT é a API. Ela é respaldada por hardware: uma vez construído, o timer roda até o próximo reset. Não há stop(), não há deinit(), não há saída por Ctrl-C. Esse é o objetivo.

Uma configuração típica no topo do main.py, imediatamente antes do laço que ele protege:

from machine import WDT

wdt = WDT(timeout=10_000)              # milliseconds

O main.py é o lugar certo para o watchdog porque é onde o laço fica. Um reset do watchdog é um reset de hardware, então o caminho de boot a frio é reexecutado e o main.py reentra no laço por conta própria – a recuperação funciona sem qualquer ligação no boot.py. Iniciar o watchdog no boot.py em vez disso significa que todo soft reset (o Ctrl-D de um desenvolvedor, por exemplo) entrega à aplicação um timer de hardware que ela não tem como parar, o que é um aborrecimento na bancada e uma armadilha em código de configuração de produção que roda antes de o laço estar pronto.

Escolha o timeout para ser 2 a 3 vezes maior do que o pior tempo de iteração observado do laço principal. Jitter na taxa de quadros, uma leitura lenta de um sensor frio, um breve soluço no Wi-Fi – nada disso deveria acionar o watchdog. Um travamento real (um laço infinito, uma chamada de E/S bloqueada) deveria. Timeouts curtos demais transformam o watchdog em fonte de resets falsos; timeouts longos demais deixam a cam parada e sem resposta por minutos antes de a recuperação disparar.

14.3.2.2. Alimentando-o

wdt.feed() reinicia a contagem regressiva. Chame-o uma vez por iteração do laço principal, no topo do corpo do laço, para que a alimentação aconteça incondicionalmente antes de qualquer trabalho que possa travar:

while True:
    wdt.feed()
    frame = csi0.snapshot()
    process(frame)

14.3.2.3. Sobrevivendo a exceções

O watchdog lida com travamentos. Exceções são um modo de falha diferente. Uma exceção não tratada sobe até o nível mais alto do script, o main.py encerra e a cam cai no REPL. O watchdog então dispara após seu timeout porque nada o está alimentando a partir do REPL, a cam reinicia e o main.py roda novamente – então a recuperação de fato funciona, mas o campo paga um timeout completo mais reinicialização a cada falha, o traceback vai para o stdout USB que ninguém lê, e qualquer estado em memória que a aplicação mantinha é perdido.

Envolver o laço principal em um try / except de nível mais alto transforma uma falha em um evento registrado pelo qual a aplicação continua, sem pagar por um reset:

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 (e não BaseException) mantém KeyboardInterrupt e SystemExit funcionando, que é o que um desenvolvedor conectado por USB quer.

Esse padrão é a metade de software da vivacidade: o watchdog captura os travamentos, o wrapper captura as falhas, e o log registra o que cada um deles capturou.

14.3.2.4. Sabendo por que um boot aconteceu

Todo soft reset e todo reset do watchdog acaba aparecendo como um novo boot. O auxiliar de diagnóstico de tempo de boot registra machine.reset_cause() a cada início a frio; a linha reset cause é o que diz ao campo se a recuperação de fato disparou ou se a cam apenas ciclou a energia normalmente.

A linha de causa do reset é o que torna visível no log o trabalho do watchdog. Um log cheio de resets watchdog timeout indica que a aplicação vem travando e o watchdog vem recuperando-a. Um log sem eles indica que o watchdog não precisou disparar – o que normalmente significa que a aplicação está saudável, mas também pode significar que o timeout está definido como longo demais para capturar os travamentos que estão de fato acontecendo.

14.3.2.5. Um ponto de partida completo

Um main.py que reúne o watchdog, a configuração de logging, o diagnóstico de tempo de boot e o wrapper se parece com isto:

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 restante deste scaffold não muda entre produtos. Hardening é um watchdog, um wrapper e um boot registrado a cada início a frio – não muito código, e a diferença entre uma cam que se recupera sozinha e uma que precisa de uma chamada de serviço.