ГлавнаяАкадемияДатчики и входы: нормализация сигналов → Разработка watchdog-сценария: периодическая проверка msg.timestamp

Разработка watchdog-сценария: периодическая проверка msg.timestamp

Урок 2 · Датчики и входы: нормализация сигналов · 30 мин · theory

Введение в Watchdog-сценарии в Node-RED

В мире промышленной автоматизации и умных зданий надежность является не опцией, а требованием. Сбой даже одного критически важного датчика, например, датчика протечки или температуры в котельной, может привести к серьезным последствиям: от материального ущерба до аварийных ситуаций. Для предотвращения таких сценариев используется концепция watchdog-таймера (от англ. "сторожевой таймер").

> 📋 Ключевые понятия:

> Watchdog-таймер — это механизм, который следит за работоспособностью системы или ее компонента. Если отслеживаемый компонент не подает признаков жизни (не "сбрасывает" таймер) в течение заданного интервала, watchdog инициирует процедуру восстановления или оповещения.

В контексте платформы HI и среды Node-RED, watchdog-сценарий — это специализированный поток, задача которого — не обрабатывать данные от датчиков, а активно контролировать их актуальность. Вместо того чтобы пассивно ждать, не придет ли сообщение, watchdog-сценарий сам периодически "просыпается" и задает вопрос: "А как давно я получал данные от этого датчика?". Если ответ "слишком давно", запускается аварийная логика.

Высокоуровневая схема такого потока выглядит следующим образом:

  • Периодический запуск: Узел `Inject` с заданным интервалом (например, каждые 30 секунд) отправляет пустое сообщение, инициируя проверку.
  • Проверка временной метки: Узел `Function` получает это сообщение, считывает из памяти время последнего полученного от датчика сообщения и сравнивает его с текущим временем.
  • Условное ветвление: Если разница во времени (дельта) превышает заданный порог, узел `Function` формирует сообщение о сбое. В противном случае — сообщение о штатной работе.
  • Выполнение действия: Узел `Switch` направляет сообщение по разным путям: "все в порядке" или "тревога". Ветвь "тревога" подключается к сценариям оповещения, перевода системы в безопасный режим или логирования инцидента.
  • Ключевое отличие этого подхода от простого ожидания, которое можно реализовать, например, с помощью узла `Trigger`, заключается в его активной природе. Пассивное ожидание (timeout) сработает, если сообщение не пришло после предыдущего. Но если датчик просто перестал отправлять данные, ничего не произойдет. Активный watchdog-сценарий, напротив, гарантированно обнаружит "замолчавший" датчик, так как проверка инициируется независимо от потока данных самого датчика.

    ---

    Архитектура потока: сохранение и чтение временной метки

    Для реализации надежного watchdog-сценария необходимо грамотно управлять состоянием, а именно — хранить временную метку последнего "живого" сообщения от датчика. В Node-RED для этого предназначен механизм контекста.

    > 💡 Подсказка: Используйте контекст потока (`flow context`) для переменных, которые нужны только в рамках одной логической группы (например, watchdog для одного датчика). `global context` предназначен для данных, разделяемых между всеми потоками и вкладками. Использование `flow context` минимизирует риск конфликтов имен и делает логику потока более инкапсулированной и понятной.

    Правильная архитектура предполагает разделение логики на два независимых, слабо связанных потока:

  • Поток данных датчика: Его основная задача — получать данные от физического устройства (через Modbus, 1-Wire, MQTT и т.д.), обрабатывать их и, как дополнительный шаг, обновлять временную метку в `flow context`. Этот поток ничего не знает о существовании watchdog'а.
  • Поток контроля (Watchdog): Его задача — периодически запускаться, читать временную метку из `flow context` и выполнять проверку. Этот поток ничего не знает о том, как именно получаются данные от датчика.
  • Такое разделение является примером хорошего проектирования и соответствует паттерну "Переиспользуемый компонент", так как каждый поток выполняет одну четко определенную функцию. Это значительно упрощает отладку, тестирование и дальнейшую модификацию системы.

    Работа с контекстом

    Для взаимодействия с `flow context` внутри узла `Function` используются два основных метода:

    Роль узла `Inject`

    Узел `Inject` в watchdog-потоке играет роль метронома или "сердца" системы мониторинга. Он настраивается на периодический запуск и отправляет пустое инициирующее сообщение, которое запускает всю цепочку проверки. Выбор интервала запуска — важный аспект проектирования, который напрямую влияет на отзывчивость системы и нагрузку на контроллер. Для большинства некритичных систем (например, мониторинг комнатной температуры) интервал в 30-60 секунд является оптимальным.

    Таким образом, архитектура представляет собой элегантную систему, где один поток является "поставщиком" данных о своем состоянии, а второй — независимым "контролером", который это состояние верифицирует.

    ---

    Практика: Модификация потока датчика для сохранения msg.timestamp

    Рассмотрим типовой поток, получающий данные от датчика температуры DS18B20, подключенного к универсальному входу контроллера по шине 1-Wire. Как мы уже выяснили в уроке `COURSE-04-M07-L02`, каждое сообщение от датчика должно содержать временную метку. Наша задача — модифицировать этот поток так, чтобы он сохранял эту метку для последующей проверки.

    Предположим, у нас есть следующий поток:

    // Исходный поток данных от датчика температуры
    

    [1-Wire In] -> [Function: "Форматирование данных"] -> [MQTT Out]

  • Добавление узла `Function`: В разрыв между узлами "Форматирование данных" и "MQTT Out" добавим новый узел `Function`. Назовем его "Сохранить Last Seen".
  • Написание кода: Откроем добавленный узел и введем следующий JavaScript-код:
  • /*
    

    * Этот узел сохраняет временную метку из сообщения

    * в контекст потока (flow context).

    * Имя переменной должно быть уникальным для каждого

    * отслеживаемого датчика.

    */

    // 1. Убедимся, что msg.timestamp существует, как определено в "Контракте сообщения"

    if (msg.timestamp && typeof msg.timestamp === 'number') {

    // 2. Сохраняем временную метку в переменную с осмысленным именем

    flow.set('last_seen_temp_living_room', msg.timestamp);

    // 3. Выводим статус для наглядности в редакторе Node-RED

    // Это является хорошей практикой и соответствует паттерну "Визуальный статус"

    let date = new Date(msg.timestamp).toLocaleTimeString();

    node.status({ fill: "blue", shape: "dot", text: "Last seen: " + date });

    } else {

    // Если метки нет, это ошибка в архитектуре потока. Логируем ее.

    node.warn("Отсутствует msg.timestamp в сообщении от датчика. Watchdog не сможет работать.");

    node.status({ fill: "red", shape: "ring", text: "Timestamp missing!" });

    }

    // 4. Возвращаем исходное сообщение БЕЗ ИЗМЕНЕНИЙ, чтобы не нарушать

    // дальнейшую работу потока (например, отправку в MQTT).

    return msg;

    После добавления этого узла наш поток будет выглядеть так:

    [1-Wire In] -> ["Форматирование"] -> [Function: "Сохранить Last Seen"] -> [MQTT Out]
    

    Важность `return msg;`

    Обратите внимание на последнюю строку кода: `return msg;`. Это критически важный элемент. Узел `Function` в Node-RED может либо остановить поток (если вернуть `null` или ничего не возвращать), либо передать сообщение дальше. Если бы мы забыли эту строку, данные от датчика никогда бы не дошли до узла `MQTT Out`, и система перестала бы работать штатно.

    Теперь при каждом успешном получении данных от датчика в `flow context` будет автоматически обновляться переменная `last_seen_temp_living_room`, содержащая время этого события в формате Unix epoch. Мы подготовили почву для создания нашего "сторожевого пса".

    ---

    Практика: Создание Watchdog-потока

    Теперь, когда у нас есть механизм сохранения времени последнего ответа датчика, мы можем создать отдельный, независимый поток для его контроля.

    > ⚠️ Внимание: Выбирайте интервал проверки и порог срабатывания (threshold) с запасом. Слишком короткий интервал может привести к ложным срабатываниям из-за нормальных сетевых задержек или кратковременной пиковой нагрузки на контроллер. Общее правило: порог должен быть как минимум в 2-3 раза больше, чем нормальный интервал отправки данных датчиком. Если датчик шлет данные каждые 60 секунд, разумный порог — 150-180 секунд.

    Создадим новый поток на той же вкладке:

  • Настройка узла `Inject`:
  • * Добавьте узел `Inject` на холст.

    * В настройках узла установите `Payload` в значение `timestamp` (это отправит текущее время, что удобно для отладки).

    * В разделе `Repeat` выберите `interval` и установите значение, например, `30 seconds`.

    * Назовите узел "Запуск проверки каждые 30с".

  • Конфигурация узла `Function`:
  • * Добавьте узел `Function` и соедините его с выходом узла `Inject`.

    * Назовите узел "Проверить актуальность данных".

    * Введите следующий код:

    // Пороговое значение в миллисекундах.
    

    // Если с момента последнего сообщения прошло больше времени,

    // считаем датчик "оффлайн". 90 секунд = 90000 мс.

    const THRESHOLD = 90000;

    // Получаем время последнего сообщения, сохраненное другим потоком

    const lastSeenTimestamp = flow.get('last_seen_temp_living_room');

    // Проверяем, было ли значение вообще установлено.

    // Это важно при первом запуске контроллера.

    if (typeof lastSeenTimestamp === 'undefined') {

    node.status({ fill: "yellow", shape: "ring", text: "Ожидание первых данных..." });

    // Прерываем выполнение, т.к. сравнивать не с чем

    return null;

    }

    // Получаем текущее время

    const currentTime = Date.now();

    // Вычисляем разницу

    const timeDelta = currentTime - lastSeenTimestamp;

    // Сравниваем с порогом

    if (timeDelta > THRESHOLD) {

    // Датчик не отвечает! Формируем сообщение о сбое.

    msg.payload = {

    status: "offline",

    deviceName: "Датчик температуры в гостиной",

    lastSeen: new Date(lastSeenTimestamp).toISOString(),

    timeOffline_ms: timeDelta

    };

    node.status({ fill: "red", shape: "dot", text: "ОФФЛАЙН! " + Math.round(timeDelta / 1000) + "с" });

    } else {

    // Все в порядке. Формируем сообщение о штатной работе.

    msg.payload = {

    status: "online",

    deviceName: "Датчик температуры в гостиной"

    };

    node.status({ fill: "green", shape: "dot", text: "Онлайн" });

    }

    // Передаем сформированное сообщение дальше

    return msg;

  • Использование узла `Switch`:
  • * Добавьте узел `Switch` и соедините его с выходом узла `Function`.

    * В настройках узла в поле `Property` укажите `msg.payload.status`.

    * Добавьте два правила:

    1. `==` (строка) `online` -> Выход 1

    2. `==` (строка) `offline` -> Выход 2

    * Назовите узел "Статус датчика?".

    Теперь наш watchdog-поток полностью готов. Он будет каждые 30 секунд проверять "здоровье" датчика и направлять результат проверки на соответствующий выход узла `Switch`. Верхний выход будет активироваться, если все хорошо, а нижний — если датчик "замолчал".

    // Watchdog-поток для датчика температуры
    

    [Inject: 30s] -> [Function: "Проверить актуальность"] -> [Switch: "Статус?"] --+-- (Выход 1: online)

    |

    +-- (Выход 2: offline)

    ---

    Реализация аварийного сценария

    Сама по себе диагностика "оффлайн" состояния бесполезна, если на нее нет реакции. Второй выход узла `Switch` — это точка, где начинается реальная работа по обеспечению отказоустойчивости. Рассмотрим три практических примера аварийных сценариев.

    Пример 1: Отправка PUSH-уведомления администратору

    Самая простая и эффективная реакция — немедленное оповещение ответственного персонала.

  • Подключите к второму выходу узла `Switch` новый узел `Function` с названием "Сформировать уведомление".
  • Код для узла:
  •     // msg.payload от watchdog-узла:

    // { status: "offline", deviceName: "...", ... }

    let device = msg.payload.deviceName;

    let offlineSeconds = Math.round(msg.payload.timeOffline_ms / 1000);

    msg.topic = "system/alerts"; // Определяем MQTT-топик для тревог

    msg.payload = {

    level: "critical",

    source: "watchdog_service",

    message: `Внимание! Потеряна связь с устройством: ${device}. Нет данных более ${offlineSeconds} секунд.`

    };

    // Для совместимости с некоторыми системами уведомлений (Telegram)

    // можно сформировать простую строку

    // msg.payload = `Внимание! Потеряна связь с устройством: ${device}.`;

    return msg;

  • Подключите к выходу этого узла `MQTT Out`, настроенный на отправку сообщений в топик `system/alerts`. Внешняя система мониторинга или бот в мессенджере, подписанный на этот топик, мгновенно доставит тревожное сообщение.
  • Пример 2: Перевод системы в безопасное состояние

    Рассмотрим более серьезный случай: перестал отвечать датчик температуры в системе водяного теплого пола. Продолжать работу насоса и котла без контроля температуры — опасно.

  • Подключите к второму выходу узла `Switch` узел `Modbus-Write` (или другой узел управления, в зависимости от оборудования).
  • Настройте узел на отправку команды выключения соответствующего реле или циркуляционного насоса. Например, запись значения `false` в Coil, отвечающий за реле насоса.
  • Это действие немедленно переводит систему в предопределенное безопасное состояние (fail-safe), предотвращая перегрев и возможные повреждения.
  • Пример 3: Запись события в журнал аудита

    Для последующего анализа причин сбоев и ведения статистики надежности оборудования крайне важно логировать все инциденты. На контроллере HI доступна база данных MySQL, которую мы можем использовать для этого.

  • Подключите к второму выходу узла `Switch` узел `Function` с названием "Формат для SQL". Код:
  •     let device = msg.payload.deviceName;

    let query = `

    INSERT INTO audit_log (timestamp, level, source, event_type, details)

    VALUES (

    NOW(),

    'ERROR',

    'WatchdogMonitor',

    'SENSOR_OFFLINE',

    '${device}'

    );

    `;

    msg.topic = query; // Узел `mysql` принимает SQL-запрос в msg.topic

    return msg;

  • Подключите к его выходу узел `mysql`, настроенный для подключения к локальной базе данных.
  • Каждый сбой теперь будет документирован с точной временной меткой, что неоценимо при проведении "разбора полетов" и приемо-сдаточных испытаниях. Этот подход полностью соответствует паттерну "Обработка ошибок" и является признаком профессионально спроектированной системы.
  • ---

    Итоги и лучшие практики

    В этом уроке мы разработали и внедрили мощный и надежный механизм контроля доступности датчиков — watchdog-сценарий. Мы увидели, как элегантная двухпоточная архитектура, построенная на разделении ответственности, позволяет создать отказоустойчивое и легко обслуживаемое решение.

    Ключевые выводы:

    Лучшие практики для внедрения watchdog-сценариев:

  • Осмысленное именование переменных: Вместо `flow.set('last_seen', ...)` используйте подробные имена, включающие тип и расположение датчика, например: `flow.set('wd_lastseen_leak_sens_kitchen', ...)`. Префикс `wd_` (watchdog) помогает мгновенно понять назначение переменной при отладке.
  • Конфигурируемые пороги срабатывания: Жестко задавать порог (threshold) прямо в коде — не лучшая практика. Более гибкий подход — задавать его через переменные контекста в отдельном узле `Inject`, который срабатывает один раз при запуске.
  • * Создайте узел `Inject` (с галочкой "Inject once after 0.1 seconds").

    * Подключите к нему узел `Function`, который инициализирует все пороги:

          flow.set('wd_threshold_temp_living_room', 90000); // 90 сек

    flow.set('wd_threshold_leak_sens_kitchen', 3600000); // 1 час

    return null;

    * В коде watchdog-узла получайте порог из контекста: `const THRESHOLD = flow.get('wd_threshold_temp_living_room') || 90000;`. Это позволяет менять настройки чувствительности без редактирования основной логики.

  • Масштабирование через субпотоки (Subflows): Если у вас десятки однотипных датчиков, не копируйте watchdog-потоки. Создайте субпоток, который принимает в качестве переменных окружения имя переменной `last_seen` и пороговое значение. Это кардинально упрощает поддержку и соответствует паттерну Node-RED "Переиспользуемый компонент".
  • Что дальше?

    Теперь, когда мы умеем обнаруживать "мертвые" датчики, на следующем занятии мы рассмотрим, как применять этот же подход для контроля исполнительных механизмов, проверяя, что отправленная команда действительно была выполнена, и реализуя механизм обратной связи.