14.3.2. Сторожовий таймер¶
Апаратний сторожовий таймер – це основа, на якій тримаються всі інші рішення щодо захисту. Це крихітний незалежний таймер, який скидає процесор, якщо за тривалий час йому нічого не повідомили. Скрипт, що завис на ненадійному датчику, мережевий виклик, який заблокувався після закінчення таймауту, алокатор пам’яті, застряглий у куті купи, виняток, що вийшов з циклу – жодна з цих ситуацій не зупиняє сторожовий таймер. Таймер відраховує незалежно, і камера перезавантажується.
Для поставленого продукту сторожовий таймер не є необов’язковим. Без нього будь-який із вищезазначених режимів відмови залишає камеру непрацюючою, доки хтось не помітить і не вимкне та не ввімкне живлення. З ним камера самостійно відновлюється, а єдиним свідченням відмови є один рядок у журналі.
Дивись також
Сторінка апаратного розділу сторожовий таймер охоплює, що таке сторожовий таймер на апаратному рівні та основи API machine.WDT. Ця сторінка охоплює, що змінюється для виробничого розгортання.
14.3.2.1. Запуск сторожового таймера¶
machine.WDT – це API. Він підкріплений апаратно: після ініціалізації таймер працює до наступного скидання. Немає stop(), немає deinit(), немає виходу через Ctrl-C. У цьому і є сенс.
Типове налаштування на початку main.py, безпосередньо перед циклом, який він захищає:
from machine import WDT
wdt = WDT(timeout=10_000) # milliseconds
main.py є правильним місцем для сторожового таймера, оскільки саме там знаходиться цикл. Скидання сторожового таймера – це апаратне скидання, тому повторно виконується шлях холодного завантаження і main.py самостійно повторно входить у цикл – відновлення працює без жодного підключення в boot.py. Запуск сторожового таймера в boot.py замість цього означає, що кожне м’яке скидання (наприклад, Ctrl-D розробника) передає застосунку апаратний таймер, який він не може зупинити, що є незручністю на лаві і пасткою у виробничому коді налаштування, який виконується до готовності циклу.
Виберіть таймаут у 2-3 рази довший за найгірший спостережуваний час ітерації головного циклу. Тремтіння частоти кадрів, повільне зчитування датчика на холодному датчику, короткий збій Wi-Fi – жодне з цього не повинно спрацьовувати сторожовий таймер. Справжнє зависання (нескінченний цикл, заблокований виклик введення/виведення) повинно. Надто короткі таймаути перетворюють сторожовий таймер на джерело хибних скидань; надто довгі таймаути дозволяють камері залишатися непридатною для використання протягом хвилин до спрацювання відновлення.
14.3.2.2. Підживлення¶
wdt.feed() скидає зворотний відлік. Викликайте його один раз на ітерацію головного циклу, на початку тіла циклу, щоб підживлення відбувалося безумовно перед будь-якою роботою, яка може зависнути:
while True:
wdt.feed()
frame = csi0.snapshot()
process(frame)
14.3.2.3. Виживання після винятків¶
Сторожовий таймер обробляє зависання. Винятки – це інший режим відмови. Необроблений виняток спливає на верхній рівень скрипту, main.py завершується, і камера переходить у REPL. Потім сторожовий таймер спрацьовує після закінчення таймауту, оскільки нічого не підживлює його з REPL, камера скидається, і main.py виконується знову – тож відновлення працює, але поле платить повний таймаут плюс перезавантаження за кожен збій, трасування стека надходить на USB stdout, який ніхто не читає, і будь-який стан в пам’яті, який зберігав застосунок, втрачено.
Обгортання головного циклу у верхньорівневий try / except перетворює збій на зареєстровану подію, через яку застосунок продовжує роботу без скидання:
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.
Цей шаблон є програмною половиною живучості: сторожовий таймер перехоплює зависання, обгортка перехоплює збої, а журнал записує, що перехопило кожне з них.
14.3.2.4. Знання причини завантаження¶
Кожне м’яке скидання і кожне скидання сторожового таймера зрештою виглядає як свіже завантаження. Допоміжний засіб діагностики при завантаженні записує machine.reset_cause() під час кожного холодного запуску; рядок reset cause показує, чи насправді спрацювало відновлення, чи камера просто нормально перезапустилася.
Рядок причини скидання робить роботу сторожового таймера видимою в журналі. Журнал, повний скидань watchdog timeout, означає, що застосунок зависав, а сторожовий таймер відновлював його. Журнал без них означає, що сторожовому таймеру не довелося спрацьовувати – зазвичай це означає, що застосунок здоровий, але також може означати, що таймаут встановлено занадто довгим для перехоплення зависань, які насправді відбуваються.
14.3.2.5. Повний стартовий шаблон¶
main.py, який об’єднує сторожовий таймер, налаштування журналювання, діагностику при завантаженні та обгортку, виглядає так:
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() – це покрокова робота застосунку; решта цього каркасу не змінюється між продуктами. Захист – це один сторожовий таймер, одна обгортка і зареєстроване завантаження при кожному холодному запуску – невеликий обсяг коду, але різниця між камерою, яка відновлюється самостійно, і тією, якій потрібен сервісний виклик.