14.3.2. Il watchdog¶
Il watchdog hardware è la base su cui poggiano tutte le altre scelte di hardening. È un minuscolo timer indipendente che resetta il processore quando non gli viene comunicato il contrario per troppo tempo. Uno script che si blocca su un sensore instabile, una chiamata di rete che resta bloccata oltre il suo timeout, un allocatore di memoria incastrato in un angolo dell’heap, un’eccezione sfuggita al loop – nessuno di questi ferma il watchdog. Il timer continua il conto alla rovescia comunque, e la cam si riavvia.
Per un prodotto spedito, un watchdog non è opzionale. Senza di esso, una qualsiasi delle modalità di guasto sopra descritte lascia la cam morta finché qualcuno non se ne accorge e non la riavvia spegnendola e riaccendendola. Con esso, la cam si rialza da sola e l’unica prova del guasto è una riga nel log.
Vedi anche
La pagina watchdog timer del capitolo sull’hardware spiega cos’è un watchdog a livello hardware e le basi dell’API machine.WDT. Questa pagina spiega cosa cambia per un deployment in produzione.
14.3.2.1. Avviare il watchdog¶
machine.WDT è l’API. È supportata dall’hardware: una volta costruito, il timer corre fino al reset successivo. Non c’è alcun stop(), nessun deinit(), nessuna via di fuga con Ctrl-C. Questo è il punto.
Una configurazione tipica all’inizio di main.py, immediatamente prima del loop che protegge:
from machine import WDT
wdt = WDT(timeout=10_000) # milliseconds
main.py è la sede giusta per il watchdog perché è lì che vive il loop. Un reset del watchdog è un reset hardware, quindi il percorso di avvio a freddo viene rieseguito e main.py rientra da solo nel loop – il recupero funziona senza alcun cablaggio in boot.py. Avviare il watchdog in boot.py significa invece che ogni soft reset (un Ctrl-D di uno sviluppatore, per esempio) consegna all’applicazione un timer hardware che non ha modo di fermare, il che è una seccatura al banco e una trappola nel codice di setup di produzione che gira prima che il loop sia pronto.
Scegli il timeout in modo che sia da 2 a 3 volte più lungo del tempo di iterazione peggiore osservato del loop principale. Il jitter del frame rate, una lettura lenta da un sensore freddo, un breve singhiozzo del Wi-Fi – nessuno di questi dovrebbe far scattare il watchdog. Un vero blocco (un loop infinito, una chiamata I/O bloccata) dovrebbe farlo. Timeout troppo brevi trasformano il watchdog in una fonte di reset falsi; timeout troppo lunghi lasciano la cam inerte per minuti prima che scatti il recupero.
14.3.2.2. Alimentarlo¶
wdt.feed() reimposta il conto alla rovescia. Chiamalo una volta per iterazione del loop principale, all”inizio del corpo del loop in modo che il feed avvenga incondizionatamente prima di qualsiasi lavoro che potrebbe bloccarsi:
while True:
wdt.feed()
frame = csi0.snapshot()
process(frame)
14.3.2.3. Sopravvivere alle eccezioni¶
Il watchdog gestisce i blocchi. Le eccezioni sono una modalità di guasto diversa. Un’eccezione non gestita risale fino al livello superiore dello script, main.py termina e la cam scende al REPL. Il watchdog scatta poi dopo il suo timeout perché nulla lo alimenta dal REPL, la cam si resetta e main.py viene eseguito di nuovo – quindi il recupero funziona davvero, ma sul campo si paga un timeout completo più il riavvio per ogni crash, il traceback va sullo stdout USB che nessuno legge, e qualsiasi stato in memoria che l’applicazione manteneva è perso.
Avvolgere il loop principale in un try / except di livello superiore trasforma un crash in un evento registrato che l’applicazione supera, senza pagare un 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")
Catturare Exception (non BaseException) mantiene funzionanti KeyboardInterrupt e SystemExit, che è ciò che vuole uno sviluppatore collegato via USB.
Questo schema è la metà software del concetto di liveness: il watchdog cattura i blocchi, il wrapper cattura i crash, e il log registra ciò che uno dei due ha catturato.
14.3.2.4. Sapere perché è avvenuto un avvio¶
Ogni soft reset e ogni reset del watchdog finisce prima o poi per manifestarsi come un nuovo avvio. L’helper di diagnostica all’avvio registra machine.reset_cause() a ogni avvio a freddo; la riga reset cause è ciò che dice sul campo se il recupero è davvero scattato oppure se la cam si è semplicemente riavviata normalmente spegnendosi e riaccendendosi.
La riga della causa del reset è ciò che rende visibile nel log il lavoro del watchdog. Un log pieno di reset watchdog timeout indica che l’applicazione si è bloccata e che il watchdog l’ha recuperata. Un log senza di essi indica che il watchdog non ha dovuto scattare – il che di solito significa che l’applicazione è in salute, ma può anche significare che il timeout è impostato troppo lungo per catturare i blocchi che si stanno effettivamente verificando.
14.3.2.5. Un punto di partenza completo¶
Un main.py che mette insieme watchdog, configurazione del logging, diagnostica all’avvio e il wrapper si presenta così:
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() è il lavoro per iterazione dell’applicazione; il resto di questa impalcatura non cambia tra prodotti. L’hardening è un watchdog, un wrapper e un avvio registrato a ogni accensione a freddo – non molto codice, e la differenza tra una cam che si recupera da sola e una che richiede un intervento di assistenza.