14.3.2. De watchdog

De hardwarematige watchdog is de bodem waarop elke andere hardeningkeuze rust. Het is een kleine, onafhankelijke timer die de processor reset wanneer hem te lang niet iets anders is opgedragen. Een script dat vastloopt op een onbetrouwbare sensor, een netwerkaanroep die langer blokkeert dan zijn time-out, een geheugenallocator die vastzit in een hoek van de heap, een uitzondering die aan de lus ontsnapte – geen daarvan stopt de watchdog. De timer telt hoe dan ook af, en de cam start opnieuw op.

Voor een geleverd product is een watchdog niet optioneel. Zonder watchdog laat elk van de bovenstaande faalmodi de cam dood liggen totdat iemand het opmerkt en hem opnieuw inschakelt. Met watchdog komt de cam vanzelf weer op en is het enige bewijs van de storing een regel in het logboek.

Zie ook

De pagina watchdog-timer in het hardwarehoofdstuk behandelt wat een watchdog is op hardwareniveau en de basis van de machine.WDT-API. Deze pagina behandelt wat er verandert bij een productie-uitrol.

14.3.2.1. De watchdog starten

machine.WDT is de API. Hij is hardwarematig ondersteund: zodra hij is aangemaakt, loopt de timer tot de volgende reset. Er is geen stop(), geen deinit(), geen Ctrl-C-ontsnapping. Dat is precies de bedoeling.

Een typische opzet bovenaan main.py, vlak voor de lus die hij beschermt:

from machine import WDT

wdt = WDT(timeout=10_000)              # milliseconds

main.py is de juiste plek voor de watchdog, omdat daar de lus zich bevindt. Een watchdog-reset is een hardwarereset, dus het koudstart-pad wordt opnieuw doorlopen en main.py betreedt de lus vanzelf opnieuw – herstel werkt zonder enige bedrading in boot.py. De watchdog in plaats daarvan in boot.py starten betekent dat elke softwarematige reset (bijvoorbeeld de Ctrl-D van een ontwikkelaar) de applicatie een hardwaretimer in handen geeft die hij op geen enkele manier kan stoppen, wat aan de werkbank een ergernis is en in productie-opstartcode die vóór de lus draait een valkuil.

Kies de time-out 2 tot 3 keer langer dan de slechtst waargenomen iteratietijd van de hoofdlus. Schommelingen in de framesnelheid, een trage sensoruitlezing bij een koude sensor, een korte wifi-hapering – geen daarvan zou de watchdog mogen laten afgaan. Een echte vastloper (een oneindige lus, een geblokkeerde I/O-aanroep) wel. Te korte time-outs maken de watchdog tot een bron van valse resets; te lange time-outs laten de cam minutenlang niet-reagerend liggen voordat het herstel afgaat.

14.3.2.2. Hem voeden

wdt.feed() reset het aftellen. Roep het eenmaal per iteratie van de hoofdlus aan, bovenaan het luslichaam, zodat het voeden onvoorwaardelijk gebeurt vóór elk werk dat zou kunnen vastlopen:

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

14.3.2.3. Uitzonderingen overleven

De watchdog handelt vastlopers af. Uitzonderingen zijn een andere faalmodus. Een niet-afgehandelde uitzondering borrelt op naar het hoogste niveau van het script, main.py wordt beëindigd en de cam valt terug naar de REPL. De watchdog gaat dan af na zijn time-out omdat niets hem vanuit de REPL voedt, de cam reset en main.py draait opnieuw – herstel werkt dus wel, maar het veld betaalt voor elke crash een volledige time-out plus herstart, de traceback gaat naar USB-stdout die niemand leest, en alle in-memory toestand die de applicatie bijhield is verloren.

Het inpakken van de hoofdlus in een try / except op het hoogste niveau verandert een crash in een gelogde gebeurtenis waar de applicatie doorheen blijft draaien, zonder voor een reset te betalen:

import logging

log = logging.getLogger(__name__)

while True:
    wdt.feed()
    try:
        frame = csi0.snapshot()
        process(frame)
    except Exception:
        log.exception("frame loop iteration failed")

Het opvangen van Exception (niet BaseException) houdt KeyboardInterrupt en SystemExit werkend, wat is wat een ontwikkelaar die via USB is aangesloten wil.

Dit patroon is de softwarehelft van levendigheid: de watchdog vangt de vastlopers op, de wrapper vangt de crashes op, en het logboek registreert wat een van beide opving.

14.3.2.4. Weten waarom een boot plaatsvond

Elke softwarematige reset en elke watchdog-reset duikt uiteindelijk op als een verse boot. De diagnosehelper bij het opstarten logt machine.reset_cause() bij elke koude start; de regel reset cause is wat het veld vertelt of het herstel daadwerkelijk afging of dat de cam gewoon normaal opnieuw werd ingeschakeld.

De reset-cause-regel is wat het werk van de watchdog zichtbaar maakt in het logboek. Een logboek vol watchdog timeout-resets zegt dat de applicatie vastloopt en dat de watchdog hem herstelt. Een logboek zonder die resets zegt dat de watchdog niet hoefde af te gaan – wat meestal betekent dat de applicatie gezond is, maar ook kan betekenen dat de time-out te lang is ingesteld om de vastlopers op te vangen die daadwerkelijk plaatsvinden.

14.3.2.5. Een volledig startpunt

Een main.py die watchdog, log-instelling, diagnose bij het opstarten en de wrapper samenbrengt, ziet er als volgt uit:

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() is het werk per iteratie van de applicatie; de rest van dit raamwerk verandert niet tussen producten. Hardening is een watchdog, een wrapper en een gelogde boot bij elke koude start – niet veel code, en het verschil tussen een cam die vanzelf herstelt en een die een serviceoproep nodig heeft.