14.3.2. Watchdog

Watchdog ฮาร์ดแวร์คือพื้นฐานที่ตัวเลือกการทำให้ระบบแข็งแกร่งอื่น ๆ ทั้งหมดอยู่บน มันคือตัวจับเวลาอิสระขนาดเล็กที่รีเซ็ตโปรเซสเซอร์เมื่อไม่ได้รับการบอกเป็นอย่างอื่นนานเกินไป สคริปต์ที่ติดอยู่กับเซนเซอร์ที่ไม่เสถียร การเรียกเครือข่ายที่บล็อกเกินการหมดเวลา ตัวจัดสรรหน่วยความจำที่ติดอยู่ในมุมของ heap ข้อยกเว้นที่หลุดออกจาก loop ไม่มีสิ่งใดหยุด watchdog ได้ ตัวจับเวลานับถอยหลังไม่ว่ากรณีใด และกล้องก็รีบูต

สำหรับผลิตภัณฑ์ที่จัดส่ง watchdog ไม่ใช่ตัวเลือก หากไม่มีมัน รูปแบบความล้มเหลวใด ๆ ข้างต้นจะทำให้กล้องหยุดทำงานจนกว่าจะมีคนสังเกตเห็นและรีสตาร์ทด้วยการตัดไฟ เมื่อมีมัน กล้องจะฟื้นตัวขึ้นมาเองและหลักฐานเดียวของความล้มเหลวคือหนึ่งบรรทัดในบันทึก

See also

หน้า watchdog timer ของบทฮาร์ดแวร์ครอบคลุมว่า watchdog คืออะไรในระดับฮาร์ดแวร์และพื้นฐานของ API machine.WDT หน้านี้ครอบคลุมสิ่งที่เปลี่ยนแปลงสำหรับการใช้งานในระดับการผลิต

14.3.2.1. การเริ่ม watchdog

machine.WDT คือ API มันรองรับฮาร์ดแวร์: เมื่อสร้างแล้ว ตัวจับเวลาจะทำงานจนถึงการรีเซ็ตครั้งถัดไป ไม่มี stop(), ไม่มี deinit(), ไม่มี Ctrl-C หลีกหนี นั่นคือจุดประสงค์

การตั้งค่าทั่วไปที่ด้านบนของ main.py ทันทีก่อน loop ที่มันปกป้อง:

from machine import WDT

wdt = WDT(timeout=10_000)              # milliseconds

main.py คือตำแหน่งที่เหมาะสมสำหรับ watchdog เพราะนั่นคือที่ที่ loop อยู่ การรีเซ็ต watchdog คือการรีเซ็ตฮาร์ดแวร์ ดังนั้นเส้นทางบูตเย็นจะรันใหม่และ main.py กลับเข้า loop เองโดยไม่ต้องมีการเชื่อมต่อใน boot.py การเริ่ม watchdog ใน boot.py แทนหมายความว่าทุก soft reset (Ctrl-D ของนักพัฒนาเป็นต้น) มอบตัวจับเวลาฮาร์ดแวร์ที่แอปพลิเคชันไม่มีทางหยุดได้ให้กับแอปพลิเคชัน ซึ่งเป็นสิ่งที่น่ารำคาญบนม้านั่งและเป็นกับดักในโค้ดการตั้งค่าการผลิตที่รันก่อน loop พร้อม

เลือก timeout ให้ยาวกว่าเวลาวนซ้ำที่สังเกตได้แย่ที่สุดของ main loop 2 ถึง 3 เท่า ความสั่นของอัตราเฟรม การอ่านเซนเซอร์ช้าบนเซนเซอร์เย็น การหยุดชะงักของ Wi-Fi สั้น ๆ ไม่มีสิ่งเหล่านี้ควรทริก watchdog การหยุดทำงานจริง (infinite loop, การเรียก I/O ที่บล็อก) ควรทริก timeout ที่สั้นเกินไปทำให้ watchdog กลายเป็นแหล่งของการรีเซ็ตที่ผิดพลาด timeout ที่ยาวเกินไปปล่อยให้กล้องไม่ตอบสนองเป็นนาทีก่อนที่การกู้คืนจะทำงาน

14.3.2.2. การ feed

wdt.feed() รีเซ็ตการนับถอยหลัง เรียกใช้หนึ่งครั้งต่อการวนซ้ำของ main loop ที่ ด้านบน ของ loop body เพื่อให้การ feed เกิดขึ้นโดยไม่มีเงื่อนไขก่อนงานใด ๆ ที่อาจหยุดทำงาน:

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

14.3.2.3. การรอดจากข้อยกเว้น

Watchdog จัดการการหยุดทำงาน ข้อยกเว้นคือรูปแบบความล้มเหลวที่แตกต่างกัน ข้อยกเว้นที่ไม่ได้จัดการจะฟองขึ้นไปถึงระดับบนสุดของสคริปต์ main.py ออก และกล้องลงไปที่ REPL จากนั้น watchdog ทริกหลังจาก timeout เพราะไม่มีอะไร feed มันจาก REPL กล้องรีเซ็ต และ main.py รันอีกครั้ง ดังนั้นการกู้คืนทำงาน แต่ภาคสนามต้องจ่ายค่า timeout เต็มบวกการรีบูตสำหรับทุก crash, traceback ไปที่ USB stdout ที่ไม่มีอะไรอ่าน และสถานะ in-memory ที่แอปพลิเคชันเก็บอยู่ก็หายไป

การห่อ main loop ด้วย try / except ระดับบนสุดเปลี่ยน crash เป็นเหตุการณ์ที่บันทึกไว้ที่แอปพลิเคชันดำเนินการต่อโดยไม่ต้องจ่ายค่าการรีเซ็ต:

import logging

log = logging.getLogger(__name__)

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

การจับ Exception (ไม่ใช่ BaseException) ทำให้ KeyboardInterrupt และ SystemExit ยังคงทำงาน ซึ่งเป็นสิ่งที่นักพัฒนาที่เชื่อมต่อผ่าน USB ต้องการ

รูปแบบนี้คือครึ่งซอฟต์แวร์ของความมีชีวิต: watchdog จับการหยุดทำงาน wrapper จับ crash และบันทึกบันทึกสิ่งที่ทั้งสองจับได้

14.3.2.4. การรู้ว่าทำไมการบูตจึงเกิดขึ้น

ทุก soft reset และทุก watchdog reset ในที่สุดจะปรากฏเป็นการบูตใหม่ ตัวช่วย boot-time diagnostics บันทึก machine.reset_cause() ทุกการเริ่มต้นแบบ cold boot บรรทัด reset cause คือสิ่งที่บอกภาคสนามว่าการกู้คืนทำงานจริงหรือกล้องแค่รีสตาร์ทด้วยไฟตามปกติ

บรรทัด reset-cause คือสิ่งที่ทำให้งานของ watchdog มองเห็นได้ในบันทึก บันทึกที่เต็มไปด้วย watchdog timeout resets บอกว่าแอปพลิเคชันกำลังหยุดทำงานและ watchdog กำลังกู้คืนมัน บันทึกที่ไม่มีบอกว่า watchdog ไม่ต้องทำงาน ซึ่งโดยปกติหมายความว่าแอปพลิเคชันมีสุขภาพดี แต่ยังอาจหมายความว่า timeout ตั้งยาวเกินไปจนจับ hang ที่เกิดขึ้นจริงไม่ได้

14.3.2.5. Starter ครบชุด

main.py ที่รวม watchdog, การตั้งค่าการบันทึก, boot-time diagnostics และ wrapper เข้าด้วยกันมีลักษณะดังนี้:

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() คืองานต่อการวนซ้ำของแอปพลิเคชัน ส่วนที่เหลือของ scaffold นี้ไม่เปลี่ยนแปลงระหว่างผลิตภัณฑ์ การทำให้แข็งแกร่งคือ watchdog หนึ่งตัว wrapper หนึ่งตัว และการบันทึกการบูตทุก cold start ไม่ใช่โค้ดมาก และความแตกต่างระหว่างกล้องที่กู้คืนเองได้และกล้องที่ต้องโทรหาช่างบริการ