14.3.2. Watchdog-ul¶
Watchdog-ul hardware este temelia pe care se sprijină fiecare altă alegere de întărire. Este un temporizator mic și independent care resetează procesorul atunci când nu i s-a spus altfel timp prea îndelungat. Un script care se blochează pe un senzor instabil, un apel de rețea care blochează dincolo de timpul său de expirare, un alocator de memorie blocat într-un colț al heap-ului, o excepție care a scăpat din buclă – niciuna dintre acestea nu oprește watchdog-ul. Temporizatorul numără descrescător indiferent de situație, iar camera repornește.
Pentru un produs livrat, un watchdog nu este opțional. Fără el, oricare dintre modurile de eșec de mai sus lasă camera moartă până când cineva observă și o repornește de la alimentare. Cu el, camera revine de la sine, iar singura dovadă a eșecului este o singură linie în jurnal.
Vezi și
Pagina temporizator watchdog din capitolul despre hardware acoperă ce este un watchdog la nivel hardware și elementele de bază ale API-ului machine.WDT. Această pagină acoperă ce se schimbă pentru o implementare de producție.
14.3.2.1. Pornirea watchdog-ului¶
machine.WDT este API-ul. Este susținut de hardware: odată construit, temporizatorul rulează până la următoarea resetare. Nu există stop(), nici deinit(), nicio cale de scăpare prin Ctrl-C. Acesta este scopul.
O configurare tipică în partea de sus a main.py, imediat înainte de bucla pe care o protejează:
from machine import WDT
wdt = WDT(timeout=10_000) # milliseconds
main.py este locul potrivit pentru watchdog, deoarece acolo se află bucla. O resetare prin watchdog este o resetare hardware, așa că ruta de pornire la rece se re-execută, iar main.py reintră în buclă de la sine – recuperarea funcționează fără nicio configurare în boot.py. Pornirea watchdog-ului în boot.py în schimb înseamnă că fiecare resetare software (de exemplu, un Ctrl-D al unui dezvoltator) predă aplicației un temporizator hardware pe care nu are nicio modalitate de a-l opri, ceea ce reprezintă o pacoste la bancul de lucru și o capcană în codul de configurare de producție care rulează înainte ca bucla să fie pregătită.
Alegeți timpul de expirare astfel încât să fie de 2 până la 3 ori mai lung decât cel mai prost timp de iterație observat al buclei principale. Variațiile ratei de cadre, o citire lentă de la un senzor rece, un scurt hopa Wi-Fi – niciuna dintre acestea nu ar trebui să declanșeze watchdog-ul. O blocare reală (o buclă infinită, un apel I/O blocat) ar trebui să o facă. Timpii de expirare prea scurți transformă watchdog-ul într-o sursă de resetări false; timpii de expirare prea lungi lasă camera să stea nereceptivă timp de minute înainte ca recuperarea să se declanșeze.
14.3.2.2. Alimentarea sa¶
wdt.feed() resetează numărătoarea inversă. Apelați-l o dată pe iterație a buclei principale, în partea de sus a corpului buclei, astfel încât alimentarea să se producă necondiționat înainte de orice lucru care s-ar putea bloca:
while True:
wdt.feed()
frame = csi0.snapshot()
process(frame)
14.3.2.3. Supraviețuirea excepțiilor¶
Watchdog-ul gestionează blocajele. Excepțiile sunt un mod de eșec diferit. O excepție negestionată urcă până la nivelul superior al scriptului, main.py se încheie, iar camera ajunge la REPL. Watchdog-ul se declanșează apoi după timpul său de expirare, deoarece nimic nu îl alimentează din REPL, camera se resetează, iar main.py rulează din nou – deci recuperarea funcționează, dar în teren se plătește un timp complet de expirare plus repornire pentru fiecare blocare, urmărirea stivei merge către ieșirea standard USB pe care nimeni nu o citește, iar orice stare în memorie pe care o păstra aplicația este pierdută.
Învelirea buclei principale într-un try / except de nivel superior transformă o blocare într-un eveniment înregistrat în jurnal prin care aplicația continuă, fără a plăti o resetare:
import logging
log = logging.getLogger(__name__)
while True:
wdt.feed()
try:
frame = csi0.snapshot()
process(frame)
except Exception:
log.exception("frame loop iteration failed")
Prinderea Exception (nu BaseException) menține funcționale KeyboardInterrupt și SystemExit, ceea ce dorește un dezvoltator conectat prin USB.
Acest tipar este jumătatea software a vitalității: watchdog-ul prinde blocajele, învelișul prinde căderile, iar jurnalul înregistrează ce a prins fiecare dintre ele.
14.3.2.4. Cunoașterea motivului unei porniri¶
Fiecare resetare software și fiecare resetare prin watchdog apar în cele din urmă ca o pornire nouă. Funcția ajutătoare de diagnosticare la pornire înregistrează machine.reset_cause() la fiecare pornire la rece; linia reset cause este cea care îi spune terenului dacă recuperarea s-a declanșat efectiv față de simpla repornire normală a camerei de la alimentare.
Linia cu cauza resetării este cea care face vizibilă în jurnal munca watchdog-ului. Un jurnal plin de resetări watchdog timeout spune că aplicația s-a tot blocat și că watchdog-ul a tot recuperat-o. Un jurnal fără acestea spune că watchdog-ul nu a trebuit să se declanșeze – ceea ce de obicei înseamnă că aplicația este sănătoasă, dar poate însemna și că timpul de expirare este setat prea lung pentru a prinde blocajele care se produc efectiv.
14.3.2.5. Un punct de plecare complet¶
Un main.py care reunește watchdog-ul, configurarea jurnalizării, diagnosticarea la pornire și învelișul arată astfel:
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() este munca per-iterație a aplicației; restul acestui schelet nu se schimbă de la un produs la altul. Întărirea înseamnă un watchdog, un înveliș și o pornire înregistrată la fiecare pornire la rece – nu prea mult cod, și diferența dintre o cameră care se recuperează de la sine și una care necesită un apel de service.