14.3.2. Watchdog¶
Sprzętowy watchdog to fundament, na którym opiera się każdy inny wybór dotyczący wzmacniania odporności. To niewielki niezależny licznik czasu (timer), który resetuje procesor, gdy zbyt długo nie otrzymuje innych poleceń. Skrypt zacinający się na niestabilnym sensorze, wywołanie sieciowe blokujące się poza swoim limitem czasu, alokator pamięci utknięty w zakamarku sterty, wyjątek, który wymknął się z pętli – żaden z nich nie zatrzyma watchdoga. Licznik odlicza niezależnie od tego, a kamera ponownie się uruchamia.
Dla dostarczanego produktu watchdog nie jest opcjonalny. Bez niego którykolwiek z powyższych trybów awarii pozostawia kamerę martwą, dopóki ktoś tego nie zauważy i nie wyłączy oraz nie włączy zasilania. Z nim kamera uruchamia się ponownie sama, a jedynym dowodem awarii jest jedna linia w dzienniku.
Zobacz także
Strona licznik czasu watchdog w rozdziale o sprzęcie omawia, czym jest watchdog na poziomie sprzętowym, oraz podstawy API machine.WDT. Ta strona omawia, co zmienia się w przypadku wdrożenia produkcyjnego.
14.3.2.1. Uruchamianie watchdoga¶
machine.WDT to API. Jest oparte na sprzęcie: po utworzeniu licznik działa aż do następnego resetu. Nie ma stop(), nie ma deinit(), nie ma ucieczki przez Ctrl-C. O to właśnie chodzi.
Typowa konfiguracja na początku main.py, bezpośrednio przed pętlą, którą chroni:
from machine import WDT
wdt = WDT(timeout=10_000) # milliseconds
main.py jest właściwym miejscem dla watchdoga, ponieważ to tam mieszka pętla. Reset watchdoga to reset sprzętowy, więc ścieżka zimnego rozruchu uruchamia się ponownie, a main.py samodzielnie ponownie wchodzi w pętlę – odzyskiwanie działa bez żadnego oprogramowania w boot.py. Uruchamianie watchdoga w boot.py oznacza natomiast, że każdy miękki reset (na przykład programistyczne Ctrl-D) przekazuje aplikacji sprzętowy licznik czasu, którego nie ma ona jak zatrzymać, co jest uciążliwością na stanowisku roboczym i pułapką w produkcyjnym kodzie konfiguracyjnym, który działa przed gotowością pętli.
Dobierz limit czasu tak, aby był od 2 do 3 razy dłuższy niż najgorszy zaobserwowany czas iteracji głównej pętli. Drgania częstotliwości ramek, powolny odczyt z zimnego sensora, krótka czkawka Wi-Fi – żadne z nich nie powinno wyzwolić watchdoga. Prawdziwe zawieszenie (nieskończona pętla, zablokowane wywołanie I/O) powinno. Zbyt krótkie limity czasu zamieniają watchdoga w źródło fałszywych resetów; zbyt długie limity pozwalają kamerze pozostawać nieczułą na reakcje przez wiele minut, zanim uruchomi się odzyskiwanie.
14.3.2.2. Karmienie watchdoga¶
wdt.feed() resetuje odliczanie. Wywołuj je raz na iterację głównej pętli, na początku ciała pętli, aby karmienie następowało bezwarunkowo przed jakąkolwiek pracą, która mogłaby się zawiesić:
while True:
wdt.feed()
frame = csi0.snapshot()
process(frame)
14.3.2.3. Przetrwanie wyjątków¶
Watchdog obsługuje zawieszenia. Wyjątki to inny tryb awarii. Nieobsłużony wyjątek przechodzi na najwyższy poziom skryptu, main.py kończy działanie, a kamera przechodzi do REPL. Watchdog następnie uruchamia się po upłynięciu swojego limitu czasu, ponieważ nic nie karmi go z REPL, kamera się resetuje, a main.py uruchamia się ponownie – więc odzyskiwanie faktycznie działa, ale w terenie za każdą awarię płaci się pełnym limitem czasu plus ponownym uruchomieniem, ślad stosu trafia na wyjście standardowe USB, którego nikt nie czyta, a cały stan w pamięci, który aplikacja utrzymywała, jest tracony.
Owinięcie głównej pętli w try / except najwyższego poziomu zamienia awarię w zarejestrowane zdarzenie, przez które aplikacja kontynuuje działanie, bez płacenia za 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")
Przechwytywanie Exception (a nie BaseException) utrzymuje działanie KeyboardInterrupt i SystemExit, czego oczekuje programista podłączony przez USB.
Ten wzorzec to programowa połowa żywotności: watchdog przechwytuje zawieszenia, owijka przechwytuje awarie, a dziennik rejestruje to, co przechwyciło którekolwiek z nich.
14.3.2.4. Wiedza o tym, dlaczego nastąpił rozruch¶
Każdy miękki reset i każdy reset watchdoga ostatecznie pojawia się jako świeży rozruch. Pomocnik diagnostyki czasu rozruchu rejestruje machine.reset_cause() przy każdym zimnym starcie; linia reset cause mówi w terenie, czy odzyskiwanie rzeczywiście się uruchomiło, czy też kamera po prostu normalnie wyłączyła i włączyła zasilanie.
Linia z przyczyną resetu sprawia, że praca watchdoga jest widoczna w dzienniku. Dziennik pełen resetów watchdog timeout mówi, że aplikacja się zawieszała, a watchdog ją odzyskiwał. Dziennik bez nich mówi, że watchdog nie musiał się uruchamiać – co zwykle oznacza, że aplikacja jest zdrowa, ale może też oznaczać, że limit czasu jest ustawiony zbyt długo, aby wychwycić zawieszenia, które faktycznie się zdarzają.
14.3.2.5. Kompletny szablon startowy¶
main.py łączący watchdoga, konfigurację logowania, diagnostykę czasu rozruchu i owijkę wygląda następująco:
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() to praca aplikacji wykonywana w każdej iteracji; reszta tego rusztowania nie zmienia się między produktami. Wzmacnianie odporności to jeden watchdog, jedna owijka i zarejestrowany rozruch przy każdym zimnym starcie – niewiele kodu, a różnica między kamerą, która sama się odzyskuje, a taką, która wymaga wizyty serwisowej.