ГлавнаяАкадемияNode-RED: установка, flows, msg/JSON, отладка → Subflows (подпотоки): создание и использование

Subflows (подпотоки): создание и использование

Урок 2 · Node-RED: установка, flows, msg/JSON, отладка · 30 мин · theory

Введение в Subflows: Абстракция и Переиспользование Логики

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

> 🔗 Связанный материал: Группировка потоков, рассмотренная в уроке COURSE-06-M06-L02, решает только визуальные задачи — она позволяет аккуратно организовать узлы на рабочей области. В то время как Subflows (подпотоки) работают на уровне логической переиспользуемости, инкапсулируя повторяющуюся функциональность.

Subflow — это специальный тип узла в Node-RED, который содержит внутри себя полноценный поток (flow) со своими входами и выходами. По сути, это способ создать свой собственный, кастомный узел из набора стандартных.

Ключевое отличие subflow от группы узлов:

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

  • Сокращение дублирования (Принцип DRY — Don't Repeat Yourself): Вместо того чтобы десятки раз копировать одну и ту же логику парсинга данных с Modbus-устройства, вы создаете один subflow, который выполняет эту задачу, и затем используете его экземпляры везде, где это необходимо.
  • Централизованное обновление: Если вам потребуется изменить логику (например, добавить новый параметр при парсинге или изменить формат уведомления), вы делаете это в одном месте — внутри редактора subflow. Изменения автоматически применятся ко всем его экземплярам в проекте после развертывания. Это кардинально снижает трудозатраты и риск ошибок.
  • Упрощение визуального восприятия: Сложные, громоздкие потоки, состоящие из сотен узлов, можно сделать чистыми и читаемыми, "спрятав" детали реализации в subflow. На верхнем уровне остаются только крупные логические блоки, соединенные между собой, что позволяет мгновенно понять общую архитектуру системы.
  • Subflow можно рассматривать как аналог функции в традиционном программировании. Это "черный ящик", который принимает данные на свои входы, выполняет внутри себя определенную инкапсулированную логику и отдает результат на свои выходы. Вам, как пользователю этого "черного ящика", не нужно знать его внутреннее устройство — достаточно понимать, какие данные он ожидает на входе и что предоставляет на выходе. Этот процесс называется абстракцией и является одним из столпов профессиональной разработки.

    ---

    Создание и базовая настройка Subflow

    Создать подпоток в Node-RED можно двумя способами: с нуля или из уже существующей группы узлов. Второй способ наиболее распространен, так как часто потребность в subflow возникает в процессе рефакторинга, когда вы замечаете повторяющуюся логику.

    > 💡 Подсказка: Сразу давайте subflow осмысленное имя в соответствии со стандартом именования, рассмотренным в уроке COURSE-06-M06-L01. Например, `SF - Parse WB-MSW3` или `SF - Ctrl Relay with Feedback`. Это поможет легко находить его в палитре и понимать его назначение.

    Рассмотрим пошаговый процесс создания subflow на примере готовой логики.

    Пошаговый процесс создания

  • Выделите узлы: С помощью мыши (удерживая клавишу Shift или растягивая рамку) выделите на рабочей области группу узлов, которую вы хотите превратить в subflow. Убедитесь, что вы захватили всю необходимую логику.
  • Выберите опцию в меню: Перейдите в главное меню Node-RED (иконка "гамбургер" в правом верхнем углу) и выберите пункт `Selection to Subflow`.
  • Готово! Выделенные узлы будут заменены одним новым узлом фиолетового цвета. Это и есть экземпляр вашего нового subflow. Старые соединения с внешними узлами будут автоматически подключены к входам и выходам subflow.
  • После создания subflow появляется в левой палитре узлов, в самом низу, в разделе "subflows", откуда его можно перетаскивать на рабочую область для создания новых экземпляров.

    Обзор интерфейса редактора Subflow

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

    Ключевые элементы редактора:

    Настройка внешнего вида и документации

    В окне `Edit subflow template` перейдите на вкладку Appearance. Здесь вы можете настроить, как экземпляр subflow будет выглядеть в основном потоке.

    Крайне важно уделить внимание вкладке Info. Это поле поддерживает Markdown-разметку и служит основной документацией к вашему "черному ящику". Хорошая практика — описать здесь:

  • Назначение: Что делает этот subflow?
  • Входы: Какие данные (`msg`) и на какой вход ожидает?
  • Выходы: Какие данные (`msg`) и с какого выхода отдает?
  • Свойства: Какие настраиваемые параметры (properties) у него есть и за что они отвечают.
  • Такой подход превращает subflow из простого набора узлов в полноценный, документированный и переиспользуемый компонент вашей системы.

    ---

    Конфигурация свойств (Properties) для кастомизации

    Главная сила subflow заключается не просто в инкапсуляции, а в возможности создавать настраиваемые, универсальные блоки. Представьте, что у вас есть 10 одинаковых модулей реле на шине Modbus с разными адресами (Unit ID). Создавать 10 почти идентичных subflow для каждого — плохая идея. Гораздо эффективнее создать один универсальный subflow и передавать в каждый его экземпляр уникальный Modbus-адрес. Эта задача решается с помощью Свойств (Properties).

    > ⚠️ Внимание: Избегайте использования общих имен для свойств, таких как `topic` или `payload`. Это может привести к конфликтам и путанице при чтении потока. Используйте осмысленные префиксы, например, `mqtt_topic_out` или `modbus_unit_id`.

    Свойства — это, по сути, переменные окружения (Environment Variables), которые доступны только внутри subflow. Вы определяете их на уровне "шаблона" subflow, а затем задаете конкретные значения для каждого "экземпляра" в основном потоке.

    Создание и настройка свойств

  • Откройте редактор subflow (двойной клик по экземпляру).
  • Нажмите кнопку `Edit subflow properties` вверху.
  • В открывшемся окне вы увидите таблицу `Environment Variables`. Нажмите `+add` для добавления нового свойства.
  • Каждое свойство имеет несколько параметров:

    | Параметр | Описание |

    |-------------|-------------------------------------------------------------------------------------------------------------------------|

    | 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`.

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

    ---

    Практический пример: универсальный Subflow для отправки уведомлений

    Давайте создадим с нуля полезный и переиспользуемый subflow, который будет отвечать за отправку уведомлений в Telegram. Это классическая задача, которая встречается почти в каждом проекте: от информирования о протечке до оповещения о превышении температуры.

    > ℹ️ Информация: Данный subflow можно легко адаптировать для любого другого сервиса нотификаций (Pushover, Email, SMS). Для этого нужно будет изменить только последний узел, ответственный за отправку, сохранив всю предшествующую логику форматирования, что демонстрирует мощь такого подхода.

    Задача: Создать subflow `SF - Notify Telegram`, который принимает на вход текстовое сообщение и отправляет его в указанный чат Telegram с разным форматированием в зависимости от типа уведомления (информационное, предупреждение, тревога).

    1. Настройка свойств Subflow

    Создадим пустой subflow (`Menu` -> `Subflows` -> `Create Subflow`) и определим для него два свойства:

  • Свойство `CHAT_ID`:
  • * Name: `CHAT_ID`

    * Label: `Telegram Chat ID`

    * Type: `text`

    * Description: "Идентификатор чата или пользователя для отправки сообщения."

  • Свойство `MSG_TYPE`:
  • * 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) | +--------------------------+

    +-----------------+

  • Узел `input`: Стандартный вход.
  • Узел `Switch` (Маршрутизация по типу):
  • * Property: `env.MSG_TYPE` (выбираем тип `env` и вводим имя свойства).

    * Правила:

    * `==` "info" -> выход 1

    * `==` "warn" -> выход 2

    * `==` "alert" -> выход 3

    * Таким образом, мы направляем поток по разным веткам в зависимости от настройки экземпляра. Сейчас мы для простоты объединим выходы в один узел `Function`, но в более сложных сценариях каждая ветка могла бы иметь свою уникальную логику. Для нашего примера достаточно одного узла `Function` после `Switch`.

  • Узел `Function` (Форматирование сообщения):
  • Этот узел будет принимать оригинальное сообщение из `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;

  • Узел `telegram sender`: Это узел из палитры `node-red-contrib-telegrambot-plus` (или аналогичной), настроенный на вашего Telegram-бота. Он примет на вход `msg.payload`, который мы подготовили в узле `Function`, и выполнит отправку.
  • Узел `output`: Соединим выход узла `telegram sender` с узлом `output`, чтобы сообщение `msg` (содержащее результат отправки) выходило из subflow. Это полезно для логирования или дальнейшей обработки.
  • 3. Использование Subflow в проекте

    Теперь в основном потоке можно использовать наш новый узел.

    1. Перетаскиваем узел `SF - Notify Telegram` из палитры.

    2. В его настройках указываем `Telegram Chat ID` администратора и выбираем `Message Type: Alert (🚨)`.

    3. Подключаем к нему выход узла, который детектирует протечку.

    1. Создаем еще один экземпляр `SF - Notify Telegram`.

    2. Указываем `Telegram Chat ID` общего чата объекта и оставляем `Message Type: Info (i)`.

    3. Подключаем к нему поток, который, например, сообщает о перезагрузке контроллера.

    Входящее сообщение для обоих экземпляров может быть очень простым:

    {
    

    "payload": "Протечка в коллекторном узле САНУЗЕЛ-1!"

    }

    Вся логика форматирования, добавления иконок и выбора чата инкапсулирована внутри subflow, делая основной поток чистым и сфокусированным на бизнес-логике.

    ---

    Резюме: лучшие практики и частые ошибки

    Подпотоки (subflows) — один из самых мощных инструментов в Node-RED для создания структурированных, поддерживаемых и масштабируемых проектов автоматизации. Правильное их использование отличает профессионального инженера-автоматизатора от новичка.

    > 💡 Подсказка: Вы можете экспортировать subflows в виде отдельного JSON-файла (`Menu` -> `Export` -> `Subflows`) и импортировать их в другие проекты или даже на другие контроллеры HI. Это позволяет создать вашу личную или корпоративную библиотеку проверенных, готовых решений (парсеры данных, контроллеры устройств, модули уведомлений), что значительно ускоряет разработку новых объектов.

    Краткий обзор преимуществ:

    Когда стоит и не стоит использовать Subflows

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

    * Повторяющиеся блоки логики: Любая последовательность из 3-5 и более узлов, которая используется в проекте два или более раз (например, обработка данных с однотипных датчиков, управление реле с обратной связью).

    * Сложная инкапсулируемая логика: Комплексные математические расчеты, реализация конечного автомата (FSM), протоколы обмена данными со сложным парсингом. Это позволяет скрыть детали реализации.

    * Создание стандартных интерфейсов: Например, subflow "Управление светом", который на вход принимает команды `ON`/`OFF`, а внутри может управлять DALI, Modbus или обычным реле. Это абстрагирует верхнеуровневую логику от физической реализации.

    * Уникальная логика: Если последовательность узлов используется только один раз в проекте, нет смысла превращать ее в subflow.

    * Простое визуальное разделение: Если вам нужно просто убрать "лапшу" из проводов на большой диаграмме, часто достаточно использовать узлы `Link In` / `Link Out`. Они не создают нового уровня абстракции, а работают как "беспроводные соединения".

    Частые ошибки

    Самая распространенная ошибка — создание слишком громоздких subflows-"монолитов". Инженер пытается упаковать в один подпоток огромный кусок функционала (например, "Все управление климатом"). Такой subflow становится крайне сложным для понимания, отладки и модификации.

    Правильный подход: Разбивайте сложную логику на несколько более мелких, сфокусированных и, возможно, вложенных друг в друга subflows. Например, вместо одного "монолита" `SF - Climate Control` можно создать:

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

    Что дальше

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