Разработка watchdog-сценария: периодическая проверка msg.timestamp
Введение в Watchdog-сценарии в Node-RED
В мире промышленной автоматизации и умных зданий надежность является не опцией, а требованием. Сбой даже одного критически важного датчика, например, датчика протечки или температуры в котельной, может привести к серьезным последствиям: от материального ущерба до аварийных ситуаций. Для предотвращения таких сценариев используется концепция watchdog-таймера (от англ. "сторожевой таймер").
> 📋 Ключевые понятия:
> Watchdog-таймер — это механизм, который следит за работоспособностью системы или ее компонента. Если отслеживаемый компонент не подает признаков жизни (не "сбрасывает" таймер) в течение заданного интервала, watchdog инициирует процедуру восстановления или оповещения.
В контексте платформы HI и среды Node-RED, watchdog-сценарий — это специализированный поток, задача которого — не обрабатывать данные от датчиков, а активно контролировать их актуальность. Вместо того чтобы пассивно ждать, не придет ли сообщение, watchdog-сценарий сам периодически "просыпается" и задает вопрос: "А как давно я получал данные от этого датчика?". Если ответ "слишком давно", запускается аварийная логика.
Высокоуровневая схема такого потока выглядит следующим образом:
Ключевое отличие этого подхода от простого ожидания, которое можно реализовать, например, с помощью узла `Trigger`, заключается в его активной природе. Пассивное ожидание (timeout) сработает, если сообщение не пришло после предыдущего. Но если датчик просто перестал отправлять данные, ничего не произойдет. Активный watchdog-сценарий, напротив, гарантированно обнаружит "замолчавший" датчик, так как проверка инициируется независимо от потока данных самого датчика.
---
Архитектура потока: сохранение и чтение временной метки
Для реализации надежного watchdog-сценария необходимо грамотно управлять состоянием, а именно — хранить временную метку последнего "живого" сообщения от датчика. В Node-RED для этого предназначен механизм контекста.
> 💡 Подсказка: Используйте контекст потока (`flow context`) для переменных, которые нужны только в рамках одной логической группы (например, watchdog для одного датчика). `global context` предназначен для данных, разделяемых между всеми потоками и вкладками. Использование `flow context` минимизирует риск конфликтов имен и делает логику потока более инкапсулированной и понятной.
Правильная архитектура предполагает разделение логики на два независимых, слабо связанных потока:
Такое разделение является примером хорошего проектирования и соответствует паттерну "Переиспользуемый компонент", так как каждый поток выполняет одну четко определенную функцию. Это значительно упрощает отладку, тестирование и дальнейшую модификацию системы.
Работа с контекстом
Для взаимодействия с `flow context` внутри узла `Function` используются два основных метода:
- `flow.set('variableName', value)`: Этот метод используется для записи или обновления значения переменной в контексте потока. В нашем случае мы будем сохранять временную метку из входящего сообщения.
- `flow.get('variableName')`: Этот метод используется для чтения значения переменной. Он позволяет watchdog-потоку получить доступ к временной метке, сохраненной потоком данных.
Роль узла `Inject`
Узел `Inject` в watchdog-потоке играет роль метронома или "сердца" системы мониторинга. Он настраивается на периодический запуск и отправляет пустое инициирующее сообщение, которое запускает всю цепочку проверки. Выбор интервала запуска — важный аспект проектирования, который напрямую влияет на отзывчивость системы и нагрузку на контроллер. Для большинства некритичных систем (например, мониторинг комнатной температуры) интервал в 30-60 секунд является оптимальным.
Таким образом, архитектура представляет собой элегантную систему, где один поток является "поставщиком" данных о своем состоянии, а второй — независимым "контролером", который это состояние верифицирует.
---
Практика: Модификация потока датчика для сохранения msg.timestamp
Рассмотрим типовой поток, получающий данные от датчика температуры DS18B20, подключенного к универсальному входу контроллера по шине 1-Wire. Как мы уже выяснили в уроке `COURSE-04-M07-L02`, каждое сообщение от датчика должно содержать временную метку. Наша задача — модифицировать этот поток так, чтобы он сохранял эту метку для последующей проверки.
Предположим, у нас есть следующий поток:
// Исходный поток данных от датчика температуры
[1-Wire In] -> [Function: "Форматирование данных"] -> [MQTT Out]
/*
* Этот узел сохраняет временную метку из сообщения
* в контекст потока (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` на холст.
* В настройках узла установите `Payload` в значение `timestamp` (это отправит текущее время, что удобно для отладки).
* В разделе `Repeat` выберите `interval` и установите значение, например, `30 seconds`.
* Назовите узел "Запуск проверки каждые 30с".
* Добавьте узел `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` и соедините его с выходом узла `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-уведомления администратору
Самая простая и эффективная реакция — немедленное оповещение ответственного персонала.
// 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;
Пример 2: Перевод системы в безопасное состояние
Рассмотрим более серьезный случай: перестал отвечать датчик температуры в системе водяного теплого пола. Продолжать работу насоса и котла без контроля температуры — опасно.
Пример 3: Запись события в журнал аудита
Для последующего анализа причин сбоев и ведения статистики надежности оборудования крайне важно логировать все инциденты. На контроллере HI доступна база данных MySQL, которую мы можем использовать для этого.
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;
---
Итоги и лучшие практики
В этом уроке мы разработали и внедрили мощный и надежный механизм контроля доступности датчиков — watchdog-сценарий. Мы увидели, как элегантная двухпоточная архитектура, построенная на разделении ответственности, позволяет создать отказоустойчивое и легко обслуживаемое решение.
Ключевые выводы:- Watchdog — это активный мониторинг, который надежнее пассивного ожидания.
- Разделение на поток данных и поток контроля — залог гибкости и надежности.
- `flow context` — идеальный инструмент для обмена состоянием между связанными, но независимыми потоками.
Лучшие практики для внедрения watchdog-сценариев:
* Создайте узел `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;`. Это позволяет менять настройки чувствительности без редактирования основной логики.
Что дальше?
Теперь, когда мы умеем обнаруживать "мертвые" датчики, на следующем занятии мы рассмотрим, как применять этот же подход для контроля исполнительных механизмов, проверяя, что отправленная команда действительно была выполнена, и реализуя механизм обратной связи.