Subflows (подпотоки): создание и использование
Введение в Subflows: Абстракция и Переиспользование Логики
В процессе разработки сложных систем автоматизации неизбежно возникает ситуация, когда определенные последовательности узлов и логические блоки повторяются в разных частях проекта. Это может быть обработка данных с однотипных датчиков, управление стандартными исполнительными механизмами или отправка унифицированных уведомлений. Простое копирование и вставка этих блоков приводит к "раздуванию" проекта, усложняет его поддержку и повышает вероятность ошибок при внесении изменений.
> 🔗 Связанный материал: Группировка потоков, рассмотренная в уроке COURSE-06-M06-L02, решает только визуальные задачи — она позволяет аккуратно организовать узлы на рабочей области. В то время как Subflows (подпотоки) работают на уровне логической переиспользуемости, инкапсулируя повторяющуюся функциональность.
Subflow — это специальный тип узла в Node-RED, который содержит внутри себя полноценный поток (flow) со своими входами и выходами. По сути, это способ создать свой собственный, кастомный узел из набора стандартных.Ключевое отличие subflow от группы узлов:
- Группа — это чисто визуальное объединение. Узлы внутри группы остаются частью основного потока. У них нет собственных, изолированных входов и выходов.
- Subflow — это логическая абстракция. Он превращает сложную цепочку узлов в один-единственный блок, который можно многократно использовать в разных местах проекта. Каждый такой блок называется экземпляром или инстансом (instance) subflow.
Преимущества использования подпотоков огромны и напрямую влияют на качество и обслуживаемость проекта:
Subflow можно рассматривать как аналог функции в традиционном программировании. Это "черный ящик", который принимает данные на свои входы, выполняет внутри себя определенную инкапсулированную логику и отдает результат на свои выходы. Вам, как пользователю этого "черного ящика", не нужно знать его внутреннее устройство — достаточно понимать, какие данные он ожидает на входе и что предоставляет на выходе. Этот процесс называется абстракцией и является одним из столпов профессиональной разработки.
---
Создание и базовая настройка Subflow
Создать подпоток в Node-RED можно двумя способами: с нуля или из уже существующей группы узлов. Второй способ наиболее распространен, так как часто потребность в subflow возникает в процессе рефакторинга, когда вы замечаете повторяющуюся логику.
> 💡 Подсказка: Сразу давайте subflow осмысленное имя в соответствии со стандартом именования, рассмотренным в уроке COURSE-06-M06-L01. Например, `SF - Parse WB-MSW3` или `SF - Ctrl Relay with Feedback`. Это поможет легко находить его в палитре и понимать его назначение.
Рассмотрим пошаговый процесс создания subflow на примере готовой логики.
Пошаговый процесс создания
После создания subflow появляется в левой палитре узлов, в самом низу, в разделе "subflows", откуда его можно перетаскивать на рабочую область для создания новых экземпляров.
Обзор интерфейса редактора Subflow
Чтобы отредактировать внутреннюю логику или настройки subflow, просто дважды щелкните по любому его экземпляру. Откроется специальный редактор, визуально похожий на обычную рабочую область.
Ключевые элементы редактора:
- Рабочая область: Здесь находится внутренняя логика subflow — те самые узлы, которые вы в него поместили. Вы можете добавлять, удалять и изменять их так же, как и в обычном потоке.
- Узел `input`: Представляет собой точку входа данных в subflow. Если ваша исходная логика имела несколько входящих соединений, будет создано соответствующее количество входов (`input 1`, `input 2` и т.д.).
- Узел `output`: Точка выхода данных. Все сообщения, отправленные на этот узел, будут выходить из соответствующего порта на экземпляре subflow в основном потоке. Вы можете добавить несколько выходов для разделения потоков данных (например, один для успешного результата, другой — для ошибки).
- Панель `Edit subflow template`: Доступна по кнопке вверху. Это главный центр настройки вашего subflow.
Настройка внешнего вида и документации
В окне `Edit subflow template` перейдите на вкладку Appearance. Здесь вы можете настроить, как экземпляр subflow будет выглядеть в основном потоке.
- Name: Имя subflow, которое отображается в палитре.
- Icon: Вы можете выбрать иконку из библиотеки Font Awesome (например, `fa-cogs` для обработки данных или `fa-bolt` для управления питанием) или загрузить свою.
- Color: Цвет узла в основном потоке.
- Label: Текст, который будет отображаться на самом экземпляре узла. Здесь можно использовать переменные свойств (рассмотрим далее) для отображения специфичной для экземпляра информации.
Крайне важно уделить внимание вкладке Info. Это поле поддерживает Markdown-разметку и служит основной документацией к вашему "черному ящику". Хорошая практика — описать здесь:
Такой подход превращает subflow из простого набора узлов в полноценный, документированный и переиспользуемый компонент вашей системы.
---
Конфигурация свойств (Properties) для кастомизации
Главная сила subflow заключается не просто в инкапсуляции, а в возможности создавать настраиваемые, универсальные блоки. Представьте, что у вас есть 10 одинаковых модулей реле на шине Modbus с разными адресами (Unit ID). Создавать 10 почти идентичных subflow для каждого — плохая идея. Гораздо эффективнее создать один универсальный subflow и передавать в каждый его экземпляр уникальный Modbus-адрес. Эта задача решается с помощью Свойств (Properties).
> ⚠️ Внимание: Избегайте использования общих имен для свойств, таких как `topic` или `payload`. Это может привести к конфликтам и путанице при чтении потока. Используйте осмысленные префиксы, например, `mqtt_topic_out` или `modbus_unit_id`.
Свойства — это, по сути, переменные окружения (Environment Variables), которые доступны только внутри subflow. Вы определяете их на уровне "шаблона" subflow, а затем задаете конкретные значения для каждого "экземпляра" в основном потоке.
Создание и настройка свойств
Каждое свойство имеет несколько параметров:
| Параметр | Описание |
|-------------|-------------------------------------------------------------------------------------------------------------------------|
| Name | Имя переменной, по которому вы будете обращаться к ней внутри subflow (например, `MODBUS_UNIT_ID`). |
| Label | Текст, который будет отображаться рядом с полем ввода в настройках экземпляра (например, "Modbus Unit ID устройства"). |
| Type | Тип поля ввода в UI: `text` (текст), `number` (число), `boolean` (чекбокс), `select` (выпадающий список), `icon` и другие. |
| Default | Значение по умолчанию, которое будет использоваться, если для экземпляра не задано иное. |
| Options | Для типа `select` здесь можно задать пары "значение: метка" для выпадающего списка. |
Использование свойств внутри Subflow
После того как вы определили свойство, например, `MODBUS_UNIT_ID`, вы можете получить его значение внутри subflow несколькими способами.
1. В узле `Function`:Самый гибкий способ. Доступ к переменным окружения осуществляется через объект `env`.
// Получаем значение свойства, определенного для этого экземпляра subflow
const unitId = env.get("MODBUS_UNIT_ID");
// Дальше мы можем использовать это значение для формирования запроса
msg.payload = {
'unitid': unitId,
'fc': 3,
'address': 100,
'quantity': 2
};
return msg;
2. В других узлах с помощью синтаксиса `{{}}` (Mustache):
Многие стандартные узлы (например, `Change`, `MQTT Out`, `Modbus-Getter`) поддерживают использование синтаксиса Mustache для подстановки значений. Это позволяет обойтись без узла `Function`.
- Пример для узла `Modbus-Getter`: В поле `Unit-ID` вместо фиксированного числа вы можете выбрать тип `env` и указать имя свойства `MODBUS_UNIT_ID`. Либо выбрать тип "JSONata" и написать `{{MODBUS_UNIT_ID}}`. Node-RED автоматически подставит значение, указанное в настройках конкретного экземпляра subflow.
- Пример для узла `Change`: Можно установить значение `msg.topic`, равное `{{mqtt_topic_out}}`.
Этот механизм превращает subflow в действительно универсальный инструмент. Вы проектируете общую логику один раз, а затем "конфигурируете" ее для сотен различных случаев, просто меняя значения свойств в экземплярах. Это фундамент для построения масштабируемых и легко поддерживаемых систем на платформе HI.
---
Практический пример: универсальный Subflow для отправки уведомлений
Давайте создадим с нуля полезный и переиспользуемый subflow, который будет отвечать за отправку уведомлений в Telegram. Это классическая задача, которая встречается почти в каждом проекте: от информирования о протечке до оповещения о превышении температуры.
> ℹ️ Информация: Данный subflow можно легко адаптировать для любого другого сервиса нотификаций (Pushover, Email, SMS). Для этого нужно будет изменить только последний узел, ответственный за отправку, сохранив всю предшествующую логику форматирования, что демонстрирует мощь такого подхода.
Задача: Создать subflow `SF - Notify Telegram`, который принимает на вход текстовое сообщение и отправляет его в указанный чат Telegram с разным форматированием в зависимости от типа уведомления (информационное, предупреждение, тревога).1. Настройка свойств Subflow
Создадим пустой subflow (`Menu` -> `Subflows` -> `Create Subflow`) и определим для него два свойства:
* Name: `CHAT_ID`
* Label: `Telegram Chat ID`
* Type: `text`
* Description: "Идентификатор чата или пользователя для отправки сообщения."
* Name: `MSG_TYPE`
* Label: `Message Type`
* Type: `select`
* Options (значение:метка):
* `info`: `Info (i)`
* `warn`: `Warning (⚠️)`
* `alert`: `Alert (🚨)`
* Default: `info`
2. Логика внутри Subflow
Внутренняя структура subflow будет выглядеть так:
+-----------------+ +--------------------------+
(input) -->--| Switch node |-->--| Function node (Format) |-->--[telegram sender]-->(output)
| (by MSG_TYPE) | +--------------------------+
+-----------------+
* Property: `env.MSG_TYPE` (выбираем тип `env` и вводим имя свойства).
* Правила:
* `==` "info" -> выход 1
* `==` "warn" -> выход 2
* `==` "alert" -> выход 3
* Таким образом, мы направляем поток по разным веткам в зависимости от настройки экземпляра. Сейчас мы для простоты объединим выходы в один узел `Function`, но в более сложных сценариях каждая ветка могла бы иметь свою уникальную логику. Для нашего примера достаточно одного узла `Function` после `Switch`.
Этот узел будет принимать оригинальное сообщение из `msg.payload`, добавлять к нему префикс в зависимости от типа и формировать итоговый объект для узла-отправщика.
// Получаем оригинальный текст сообщения
const originalMessage = msg.payload;
// Получаем тип сообщения из свойств экземпляра
const messageType = env.get("MSG_TYPE");
let prefix = "";
switch (messageType) {
case "info":
prefix = "ℹ️ INFO:";
break;
case "warn":
prefix = "⚠️ WARNING:";
break;
case "alert":
prefix = "🚨 ALERT:";
break;
}
// Формируем финальный текст
const finalMessage = `${prefix} ${originalMessage}`;
// Получаем Chat ID из свойств
const chatId = env.get("CHAT_ID");
// Формируем объект msg для узла node-red-contrib-telegrambot-plus
// Этот "контракт сообщения" зависит от конкретного узла, который вы используете!
msg.payload = {
chatId: chatId,
type: 'message',
content: finalMessage
};
return msg;
3. Использование Subflow в проекте
Теперь в основном потоке можно использовать наш новый узел.
- Пример 1: Уведомление о протечке (тип `alert`)
2. В его настройках указываем `Telegram Chat ID` администратора и выбираем `Message Type: Alert (🚨)`.
3. Подключаем к нему выход узла, который детектирует протечку.
- Пример 2: Информационное сообщение (тип `info`)
2. Указываем `Telegram Chat ID` общего чата объекта и оставляем `Message Type: Info (i)`.
3. Подключаем к нему поток, который, например, сообщает о перезагрузке контроллера.
Входящее сообщение для обоих экземпляров может быть очень простым:
{
"payload": "Протечка в коллекторном узле САНУЗЕЛ-1!"
}
Вся логика форматирования, добавления иконок и выбора чата инкапсулирована внутри subflow, делая основной поток чистым и сфокусированным на бизнес-логике.
---
Резюме: лучшие практики и частые ошибки
Подпотоки (subflows) — один из самых мощных инструментов в Node-RED для создания структурированных, поддерживаемых и масштабируемых проектов автоматизации. Правильное их использование отличает профессионального инженера-автоматизатора от новичка.
> 💡 Подсказка: Вы можете экспортировать subflows в виде отдельного JSON-файла (`Menu` -> `Export` -> `Subflows`) и импортировать их в другие проекты или даже на другие контроллеры HI. Это позволяет создать вашу личную или корпоративную библиотеку проверенных, готовых решений (парсеры данных, контроллеры устройств, модули уведомлений), что значительно ускоряет разработку новых объектов.
Краткий обзор преимуществ:- Чистота проекта: Сложные потоки становятся визуально простыми и понятными.
- Скорость разработки: Повторяющиеся задачи решаются перетаскиванием готового блока, а не копированием десятков узлов.
- Надежность: Изменение логики в одном месте (в шаблоне subflow) гарантирует ее консистентность во всех точках использования, исключая ошибки ручного обновления.
Когда стоит и не стоит использовать Subflows
📋 Ключевые понятия:
- Когда subflows ОБЯЗАТЕЛЬНЫ:
* Сложная инкапсулируемая логика: Комплексные математические расчеты, реализация конечного автомата (FSM), протоколы обмена данными со сложным парсингом. Это позволяет скрыть детали реализации.
* Создание стандартных интерфейсов: Например, subflow "Управление светом", который на вход принимает команды `ON`/`OFF`, а внутри может управлять DALI, Modbus или обычным реле. Это абстрагирует верхнеуровневую логику от физической реализации.
- Когда subflows могут быть избыточны:
* Простое визуальное разделение: Если вам нужно просто убрать "лапшу" из проводов на большой диаграмме, часто достаточно использовать узлы `Link In` / `Link Out`. Они не создают нового уровня абстракции, а работают как "беспроводные соединения".
Частые ошибки
Самая распространенная ошибка — создание слишком громоздких subflows-"монолитов". Инженер пытается упаковать в один подпоток огромный кусок функционала (например, "Все управление климатом"). Такой subflow становится крайне сложным для понимания, отладки и модификации.
Правильный подход: Разбивайте сложную логику на несколько более мелких, сфокусированных и, возможно, вложенных друг в друга subflows. Например, вместо одного "монолита" `SF - Climate Control` можно создать:- `SF - Read Temp Sensor` (читает, валидирует и форматирует данные)
- `SF - Control Valve` (управляет сервоприводом клапана)
- `SF - HVAC FSM` (реализует логику конечного автомата: "Нагрев", "Охлаждение", "Выкл")
Эти маленькие subflows легко тестировать по отдельности, они с большей вероятностью будут переиспользованы в других частях проекта, а основной климатический поток, состоящий из этих блоков, будет легко читаться.
Что дальше
В этом уроке мы рассмотрели, как инкапсулировать и переиспользовать логику с помощью Subflows. Мы научились создавать их, настраивать с помощью свойств и применять на практике для повышения качества и структурированности проекта. В следующем уроке мы перейдем к управлению зависимостями проекта и использованию внешних библиотек через менеджер палитры, что позволит еще больше расширить функциональность нашего контроллера.