Отлов ошибок выполнения с помощью ноды Catch
Введение в обработку ошибок в Node-RED
🔗 Связанный материал: Эта концепция является логическим продолжением урока COURSE-05-M05-L01, где мы ввели понятие 'Безопасного состояния' (Safe-State). Отлов ошибок — это механизм, который запускает переход в это состояние.
Любая система автоматизации, от простого умного дома до сложного промышленного объекта, является надежной ровно настолько, насколько надежен ее самый слабый компонент. В программном обеспечении таким "слабым звеном" часто становятся непредвиденные сбои: отключение устройства, получение некорректных данных или внутренние ошибки в логике. Игнорирование возможности таких сбоев приводит к созданию хрупких, непредсказуемых систем, которые могут отказать в самый критический момент.
Критическая важность обработки ошибок заключается в обеспечении стабильности и предсказуемости поведения контроллера. Когда в одной из нод потока (flow) возникает ошибка — например, нода `modbus-read` не может связаться с устройством из-за обрыва шины RS-485 — происходит следующее:
Это стандартное поведение является необработанным исключением. Система сообщает о проблеме, но не предпринимает никаких действий для ее компенсации. Для примера с Modbus-устройством это означает, что система будет продолжать "думать", что у реле или датчика последнее известное состояние, хотя на самом деле связь с ним потеряна. Это может привести к опасным ситуациям: система может считать, что клапан подачи воды закрыт, в то время как она потеряла возможность им управлять.
Обработанное исключение — это когда мы предвидим возможность сбоя и создаем специальную логику для реакции на него. Именно для этого в Node-RED существует нода `Catch`.Концепция централизованной обработки ошибок предполагает создание отдельного, выделенного потока, который служит "диспетчерской" для всех критических сбоев на контроллере. Вместо того чтобы обрабатывать ошибки индивидуально в каждом потоке, мы направляем все сигналы о сбоях в одно место. Этот центральный поток отвечает за:
- Аудит: Запись детальной информации об ошибке в базу данных MySQL или персистентный лог-файл для последующего анализа.
- Уведомление: Отправку сообщения администратору системы (через MQTT, Telegram, Email) о возникновении критического сбоя.
- Компенсация: Запуск сценариев для перевода системы в 'Безопасное состояние' (Safe-State), как мы рассматривали ранее.
Такой подход делает систему не просто стабильной, а отказоустойчивой. Она не просто перестает работать при сбое, а активно реагирует на него, минимизируя негативные последствия.
---
Нода Catch: Ваша основная сеть безопасности
Нода `Catch` (в русской локализации "Перехват") — это специальная нода в палитре Node-RED, которая не имеет входного порта. Ее единственная задача — "слушать" возникновение ошибок в определенной области и, при их возникновении, создавать новое сообщение `msg`, содержащее всю информацию о сбое. Она действует как глобальная сеть безопасности, которая ловит все, что пошло не так.Принцип работы и конфигурация
При размещении на листе нода `Catch` выглядит просто, но ее возможности определяются в окне конфигурации. Главный параметр — `Scope` (Область действия).
| Scope / Область действия | Описание | Применение |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| All nodes (Все ноды) | Нода будет перехватывать ошибки, сгенерированные любой нодой на всех листах (вкладках) вашего проекта Node-RED. | Идеально для создания центрального потока логирования. Одна такая нода для всего проекта. |
| Current flow (Текущий лист) | Нода будет перехватывать ошибки только от нод, расположенных на том же листе (вкладке), что и сама нода `Catch`. | Полезно для логики, специфичной для конкретного функционального блока (например, "Управление климатом"). |
| Selected nodes (Выбранные ноды) | Позволяет вручную выбрать из списка конкретные ноды, ошибки от которых нужно перехватывать. После выбора нода `Catch` будет игнорировать ошибки от всех остальных нод. | Для создания высокоприоритетной реакции на сбои только критически важного оборудования. |
Структура сообщения от ноды `Catch`
Когда нода `Catch` перехватывает ошибку, она генерирует новый объект `msg`. Ключевая информация находится в `msg.error`. Рассмотрим его структуру на примере.
{
"_msgid": "a1b2c3d4.e5f6g7",
"topic": "",
"payload": "Error: Timed out",
"error": {
"message": "Error: Timed out",
"source": {
"id": "12345678.9abcdef",
"type": "modbus-read",
"name": "Опрос счетчика электроэнергии",
"count": 1
}
}
}
📋 Ключевые понятия в `msg.error`:
- `msg.error.message` (string): Текстовое описание ошибки, сгенерированное сбойной нодой. Это первое, на что нужно смотреть для понимания сути проблемы (`"Timed out"`, `"Invalid topic"`, `"Connection lost"`).
- `msg.error.source` (object): Объект с информацией об источнике ошибки. Это бесценно для диагностики.
* `type`: Тип ноды (`function`, `mqtt in`, `modbus-read`). Помогает понять, с каким классом устройств или логики возникла проблема.
* `name`: Имя, которое вы присвоили ноде в редакторе. Это критически важно! Всегда давайте нодам осмысленные имена (`"Управлять клапаном отопления"`, а не просто `"function"`), чтобы по логам можно было сразу понять, что сломалось.
> ℹ️ Информация: В объекте `msg` от ноды `Catch` также может присутствовать копия оригинального сообщения, которое вызвало ошибку. Это полезно для отладки, так как позволяет увидеть, какие именно данные "сломали" поток.
`Catch` vs `Status`: в чем разница?
Очень важно не путать ноду `Catch` с нодой `Status`.
Нода `Status` реагирует на изменение состояния* ноды, которое разработчик этой ноды предусмотрел (`connecting`, `connected`, `disconnected`). Она используется для мониторинга штатной работы. Нода `Catch` реагирует на нештатные исключения и сбои*, которые приводят к прерыванию потока.Представьте MQTT-ноду. `Status` сообщит вам, когда она успешно подключилась к брокеру или планово отключилась. `Catch` сработает, если при попытке публикации сообщения в брокере произойдет сбой сети, и нода сгенерирует ошибку.
---
Практика: Отлов таймаута Modbus RTU
Теория важна, но понимание приходит с практикой. Давайте смоделируем одну из самых частых проблем на объектах: потерю связи с устройством на шине RS-485. Мы будем использовать встроенный порт RS-485 контроллера HI и любое Modbus RTU устройство (например, релейный модуль или счетчик).
⚠️ Внимание: Не все ноды генерируют ошибки одинаково. Формат `msg.error` может отличаться. Всегда сверяйтесь с документацией конкретной ноды (`node-red-contrib-modbus`, `node-red-contrib-knx-ultimate` и т.д.), чтобы понимать, какие ошибки она может выдавать. Ошибки от Modbus, KNX и DALI будут иметь разную структуру.
План эксперимента
Пошаговая реализация
* `Inject`: Настройте на срабатывание каждые 5 секунд.
* `modbus-read`: Настройте его на чтение любого регистра с вашего Modbus-устройства. Дайте ноде осмысленное имя, например, "Опрос реле MR-8".
* `Debug`: Подключите к выходу `modbus-read`, чтобы видеть успешные ответы.
* Перетащите ноду `Catch` на лист.
* В настройках оставьте `Scope: Current flow`.
* Подключите ее выход к другой ноде `Debug`, которую для ясности можно назвать "ЛОГ ОШИБОК".
Ваш поток должен выглядеть примерно так:
[Inject] ---> [modbus-read: "Опрос реле MR-8"] ---> [Debug: "Успешный ответ"]
[Catch] ---> [Debug: "ЛОГ ОШИБОК"]
* Разверните (Deploy) проект. В панели отладки вы должны видеть регулярные сообщения от ноды `"Успешный ответ"`.
* Теперь физически отсоедините кабель RS-485 от контроллера.
* Через несколько секунд нода `Inject` снова отправит сигнал, `modbus-read` попытается опросить устройство, но не сможет. Произойдет таймаут.
Нода `"Успешный ответ"` замолчит. Вместо этого в панели отладки вы увидите сообщение от ноды `"ЛОГ ОШИБОК"`. Его структура будет похожа на эту:
{
"_msgid": "...",
"error": {
"message": "Port Not Open",
"source": {
"id": "f4b3a2d1.cba987",
"type": "modbus-read",
"name": "Опрос реле MR-8"
},
"originalMessage": {
"payload": 1678886400000,
"topic": "",
"_msgid": "..."
}
}
}
Из этого объекта мы можем извлечь бесценную информацию для диагностики:
* `error.message`: "Port Not Open" или "Timed out" — четко указывает на проблему со связью.
* `error.source.name`: "Опрос реле MR-8" — мы точно знаем, какое именно устройство перестало отвечать. Если бы у нас было 20 Modbus-устройств, это имя позволило бы мгновенно локализовать проблему.
* `error.source.type`: "modbus-read" — подтверждает, что проблема связана с протоколом Modbus.
Теперь мы не просто знаем, что что-то сломалось. Мы знаем, что именно сломалось и почему. Это и есть первый шаг к построению по-настоящему надежной системы.
---
Построение потока для перехода в 'Safe-State'
Отловить ошибку — это лишь половина дела. Вторая, и самая важная половина, — правильно на нее отреагировать. Теперь мы объединим ноду `Catch` с концепцией "Безопасного состояния".
Задача: При потере связи с критически важным Modbus-модулем, который управляет приточной вентиляцией, мы должны немедленно перевести систему климата в безопасный режим: выключить все нагреватели и закрыть все клапаны, управляемые через другую шину, например, DALI или KNX, чтобы предотвратить перегрев или работу системы вразнос.Логика потока "Аварийный Менеджер"
Этот поток начинается с ноды `Catch` и использует ноду `Switch` для маршрутизации в зависимости от источника ошибки.
+--> [Function: "Формировать команду ВЫКЛ для DALI"] ---> [dali_out]
|
[Catch] --> [Switch: "Фильтр ошибок"] --+--> [Function: "Формировать команду ВЫКЛ для KNX"] ---> [knx_out]
|
+--> [Function: "Отправить уведомление в Telegram"] ---> [telegram_sender]
* Свойство для проверки: `msg.error.source.name`
* Правила:
* `содержит` "вентиляция" -> выход 1
* `содержит` "управление светом" -> выход 2
* `иначе` -> выход 3 (для всех остальных ошибок)
Пример кода для ветки, реагирующей на сбой модуля вентиляции:
// Сообщение пришло из ноды Catch после сбоя Modbus
// Анализируем ошибку для аудита (не обязательно для работы, но полезно)
let sourceName = msg.error.source.name;
let errorMessage = msg.error.message;
node.warn(`КРИТИЧЕСКИЙ СБОЙ: Потеря связи с '${sourceName}'. Причина: ${errorMessage}. Активация Safe-State.`);
// Формируем новую команду для выключения ВСЕХ нагревателей на шине KNX.
// Мы создаем полностью новый объект msg, т.к. старый содержит инфо об ошибке.
// Контракт сообщения для ноды KNX:
// msg.payload = false; (для выключения)
// msg.topic = '0/1/1'; (групповой адрес KNX)
// Возвращаем массив сообщений, чтобы одной функцией выключить несколько групп.
const safeStateCommands = [
{ topic: '1/1/0', payload: false, knx: {dpt: '1.001'} }, // Группа KNX "Нагреватель 1 этаж"
{ topic: '1/1/1', payload: false, knx: {dpt: '1.001'} }, // Группа KNX "Нагреватель 2 этаж"
{ topic: '2/3/5', payload: 0, knx: {dpt: '5.001'} } // Группа KNX "Клапан вентиляции" (0%)
];
// node.send() позволяет отправить несколько сообщений из одной функции.
// Каждое сообщение выйдет из ноды и будет обработано дальше.
node.send(safeStateCommands);
// Мы не возвращаем ничего через return, т.к. использовали node.send().
return null;
Этот пример демонстрирует мощь подхода: одна-единственная отловленная ошибка приводит в действие целый каскад команд, которые переводят систему в заранее определенное безопасное состояние. При этом логика аварийного реагирования полностью отделена от основной логики управления.
---
Фильтрация ошибок и ограничение области действия
По мере усложнения проекта, одна нода `Catch`, настроенная на `All nodes`, может стать источником "шума". Некоторые ошибки могут быть ожидаемыми или некритичными, и мы не хотим, чтобы они запускали аварийные сценарии. Здесь на помощь приходит более тонкая настройка области действия.
> 💡 Подсказка: На реальных объектах рекомендуется иметь как минимум две ноды `Catch`: одну общую (`Scope: All nodes`) для всеобъемлющего логирования и одну или несколько специфичных (`Scope: Selected nodes`) для реакции на сбои критически важных нод.
`All nodes` vs `Selected nodes`
- Используйте `All nodes` для глобального логгера. Создайте отдельный лист (flow) под названием "SYSTEM_AUDIT". На нем разместите одну ноду `Catch` (`Scope: All nodes`), которая будет направлять все ошибки в базу данных MySQL и отправлять уведомления администратору. Этот поток ничего не "чинит", он только протоколирует.
- Используйте `Selected nodes` для критической логики. На листе "HVAC_CONTROL" (управление климатом) разместите вторую ноду `Catch`. В ее настройках выберите `Scope: Selected nodes` и отметьте галочками только те ноды, которые отвечают за управление насосами, клапанами и нагревателями. Выход этой ноды `Catch` подключите к потоку активации "Safe-State" для климатической системы.
Таким образом, если произойдет сбой в ноде, опрашивающей декоративную подсветку, сработает только глобальный логгер. Но если откажет нода управления циркуляционным насосом, сработают обе ноды `Catch`: одна запишет это в лог, а вторая немедленно запустит аварийный сценарий.
Как избежать "шума" от ожидаемых ошибок
Представим сценарий, в котором вы сканируете шину RS-485 в поиске новых устройств. Ваш поток в цикле опрашивает все адреса с 1 по 247. Естественно, большинство этих запросов завершатся ошибкой `timeout`, так как на большинстве адресов устройств нет. Эти ошибки ожидаемы и не являются признаком сбоя.
Если использовать глобальную ноду `Catch`, она будет завалена сотнями таких сообщений. Решение — локализованная обработка ошибок.
У большинства нод ввода-вывода (включая `modbus-read`) есть второй выходной порт, который активируется при ошибке.
[Inject: "start scan"] --> [Function: "Loop 1-247"] --> [modbus-read] --+--> [Success Logic]
|
+--> [Debug: "Error, expected"]
В этом случае ошибка таймаута не будет "выброшена" на глобальный уровень и не будет поймана нодой `Catch`. Она выйдет через второй порт ноды `modbus-read` и будет обработана локально (в данном случае, просто проигнорирована или посчитана). Таким образом, мы отделяем ожидаемые ошибки от реальных, непредвиденных сбоев.
---
Итоги и лучшие практики
Мы рассмотрели один из самых важных механизмов обеспечения надежности в Node-RED. Правильное использование ноды `Catch` превращает хрупкую систему в отказоустойчивую.
📋 Ключевые выводы:
Что дальше?
В следующем уроке мы углубимся в создание полноценного централизованного потока для журналирования (logging), используя базу данных MySQL на борту контроллера HI, чтобы создать исчерпывающий аудиторский след всех событий и ошибок в системе. Мы также рассмотрим, как использовать ноду `Status` для проактивного мониторинга состояния оборудования, не дожидаясь возникновения критических ошибок.