ГлавнаяАкадемияNode-RED: установка, flows, msg/JSON, отладка → Настройка свойств Subflow (переменные окружения)

Настройка свойств Subflow (переменные окружения)

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

Введение в свойства Subflow (Environment Variables)

Как мы рассмотрели в предыдущем уроке, Subflows (подпотоки) являются краеугольным камнем для создания структурированных и легко поддерживаемых проектов в Node-RED. Они позволяют инкапсулировать повторяющуюся логику в единый блок. Однако, чтобы сделать эти блоки по-настоящему универсальными, нам необходим механизм их параметризации. Этот механизм — свойства Subflow, также известные как переменные окружения Subflow.

Представьте, что вы создали Subflow для отправки уведомлений в Telegram. Логика отправки сообщения всегда одна и та же, но что если вы хотите отправлять сообщения разным пользователям или в разные чаты? Без параметризации вам пришлось бы создать копию Subflow для каждого получателя, что свело бы на нет всю пользу от их использования. Свойства Subflow решают эту проблему, позволяя вынести изменяемые параметры (как ID чата или токен бота) за пределы основной логики.

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

> * Свойство Subflow (Environment Variable): Это именованная переменная, определенная на уровне шаблона Subflow, значение которой можно задавать для каждого его экземпляра индивидуально. Она позволяет "настраивать" поведение Subflow снаружи, не изменяя его внутреннюю структуру.

> * Параметризация: Процесс создания компонента (в данном случае Subflow), поведение которого можно изменять с помощью входных параметров (свойств), а не путем модификации его исходного кода.

Ключевое отличие свойств Subflow от других типов переменных в Node-RED заключается в их области видимости.

| Тип переменной | Область видимости | Жизненный цикл | Основное применение |

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

| Глобальный контекст | Весь экземпляр Node-RED (все потоки и вкладки) | Сохраняется между развертываниями и перезапусками | Хранение глобальных настроек, состояний всей системы |

| Контекст потока (Flow) | Все узлы на одной вкладке (flow) | Сохраняется между развертываниями | Хранение состояния, специфичного для одной вкладки |

| Свойство Subflow | Конкретный экземпляр Subflow | Задается при конфигурации экземпляра, не меняется в runtime | Параметризация переиспользуемых компонентов (API ключи, ID устройств, топики) |

| Контекст узла (Node) | Один конкретный узел | Сбрасывается при каждом развертывании (deploy) | Временное хранение состояния внутри одного узла (счетчики) |

Использование свойств Subflow предоставляет инженеру автоматизации три неоспоримых преимущества:

  • Создание универсальных блоков: Вы пишете логику один раз (например, опрос Modbus-устройства), а затем используете этот блок для десятков разных устройств, просто меняя их адреса и регистры через свойства.
  • Упрощение поддержки: Если вам нужно обновить логику опроса, вы вносите изменение в одном месте — внутри шаблона Subflow. Это изменение автоматически применяется ко всем его экземплярам в проекте.
  • Снижение количества ошибок: Жестко заданные параметры внутри копий потоков — частый источник ошибок при копировании-вставке. Параметризация через свойства делает конфигурацию явной и предсказуемой.
  • ---

    Практикум: создание и настройка свойств в редакторе

    Рассмотрим пошаговый процесс создания и настройки свойств на примере простого Subflow, который будет добавлять к сообщению префикс.

  • Создайте новый Subflow. Как мы уже знаем, для этого нужно выделить несколько узлов (например, `Inject` и `Debug`) и в меню выбрать `Subflows` -> `Selection to Subflow`.
  • Войдите в режим редактирования Subflow. Дважды щелкните по созданному блоку Subflow, чтобы открыть его содержимое на новой вкладке.
  • Откройте редактор свойств. В верхней части редактора Subflow найдите и нажмите кнопку Edit subflow properties.
  • (Примечание: здесь должна быть иллюстрация интерфейса Node-RED)

  • Добавление переменной. Вы увидите панель "Environment Variables". Нажмите кнопку +add, чтобы добавить новое свойство.
  • (Примечание: здесь должна быть иллюстрация интерфейса Node-RED)

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

    * Name: Имя переменной. Это тот идентификатор, по которому вы будете обращаться к свойству внутри Subflow. Используйте латиницу и `SNAKE_CASE` (например, `DEVICE_NAME`, `MQTT_TOPIC`).

    * Value: Значение по умолчанию. Это значение будет использоваться, если для экземпляра Subflow не будет задано иное.

    * Type: Тип данных для значения. Вы можете выбрать `string`, `number`, `boolean` и другие. Это помогает валидировать ввод.

    * Input widget: Способ отображения поля для ввода значения в настройках экземпляра. Вы можете выбрать `text input`, `checkbox`, `spinner` (для чисел) и т.д.

  • Настройка UI-элементов. Для удобства конфигурации экземпляров на основном потоке, вы можете настроить внешний вид поля ввода:
  • * Label: Текстовая метка, которая будет отображаться рядом с полем ввода. Используйте осмысленный текст на русском языке, например, "ID Modbus устройства".

    * Placeholder: Текст-подсказка, который отображается внутри пустого поля ввода. Например, "1-247".

    * Icon: Вы можете выбрать иконку из библиотеки Font Awesome для визуального выделения свойства.

    Давайте создадим свойство с именем `LOG_PREFIX`.

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

    > 💡 Подсказка: Используйте осмысленные имена для переменных, которые отражают их назначение, например, `MQTT_TOPIC_PREFIX` или `MODBUS_SLAVE_ID`. Это значительно упрощает конфигурацию экземпляров Subflow в основном потоке. Избегайте общих имен вроде `value1` или `param2`.

    ---

    Использование переменных окружения внутри Subflow

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

    Основной синтаксис: `${ENV_VAR_NAME}`

    Этот синтаксис является стандартным для большинства полей ввода в узлах Node-RED и позволяет напрямую подставлять значение переменной.

    Применение в узле `Change`:

    Предположим, мы хотим установить `msg.topic`, используя наше свойство `LOG_PREFIX`.

  • Добавьте узел `Change` внутрь Subflow.
  • Настройте правило: `Set` `msg.topic` `to` `${LOG_PREFIX}`.
  • Теперь, когда сообщение пройдет через этот узел, его `msg.topic` будет установлен в значение, заданное для данного экземпляра Subflow.

    Применение в узле `Template`:

    Этот узел идеально подходит для формирования сложных строк.

  • Добавьте узел `Template`.
  • В поле `Template` напишите: `Это сообщение от устройства с префиксом: ${LOG_PREFIX}. Данные: {{payload}}`.
  • Если свойство `LOG_PREFIX` установлено в `[BoilerRoom]`, а `msg.payload` равен `45.5`, то на выходе узла будет строка: `Это сообщение от устройства с префиксом: [BoilerRoom]. Данные: 45.5`.

    Применение в узле `mqtt out`:

    Это один из самых частых сценариев.

  • Добавьте узел `mqtt out`.
  • В поле `Topic` введите `${MQTT_ROOT_TOPIC}/status`, где `MQTT_ROOT_TOPIC` — это имя определенного вами свойства.
  • Это позволяет одному и тому же Subflow публиковать данные в разные ветки топиков, делая его универсальным для разных частей объекта.

    Доступ через узел `Function`: `env.get()`

    Внутри узла `Function` синтаксис `${...}` не работает. Вместо этого используется специальный объект `env`, который предоставляет метод `get()`.

    // Получаем значение переменной окружения с именем 'LOG_PREFIX'
    

    const prefix = env.get('LOG_PREFIX');

    // Получаем значение другого свойства, например, числового

    const timeout_ms = env.get('REQUEST_TIMEOUT');

    // Используем значения в логике

    msg.payload = prefix + ' ' + msg.payload;

    msg.timeout = timeout_ms;

    // Важно: node.status должен использовать полученное значение, а не синтаксис ${}

    node.status({ fill: "blue", shape: "dot", text: "Prefix: " + prefix });

    return msg;

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

    ---

    Переопределение свойств для экземпляра Subflow

    Сила свойств Subflow в полной мере раскрывается, когда мы начинаем использовать несколько экземпляров одного и того же Subflow в нашем проекте, но с разными настройками.

    Представим, что мы создали универсальный Subflow `SCN-MODBUS-READER` для чтения данных с Modbus-устройств. У него есть свойство `MODBUS_SLAVE_ID` с типом `number`.

    Теперь разместим два экземпляра этого Subflow на основной вкладке для опроса двух разных устройств на одной шине RS-485:

    Процесс настройки:
  • Разместите первый экземпляр: Перетащите ваш `SCN-MODBUS-READER` из палитры на рабочую область.
  • Откройте настройки: Дважды щелкните по узлу. Вы увидите стандартное окно редактирования узла, но с дополнительной вкладкой Properties.
  • (Примечание: здесь должна быть иллюстрация интерфейса Node-RED)

  • Переопределите свойство: На вкладке `Properties` вы увидите список всех переменных окружения, которые вы определили. Рядом с `MODBUS_SLAVE_ID` будет поле для ввода значения. Введите `10`. Также дайте этому экземпляру осмысленное имя, например, `Температура в гостиной`.
  • Разместите второй экземпляр: Снова перетащите `SCN-MODBUS-READER` на рабочую область.
  • Настройте его: Откройте настройки второго экземпляра, перейдите на вкладку `Properties` и введите `11` в поле `MODBUS_SLAVE_ID`. Назовите его `CO2 в гостиной`.
  • Результат:

    Теперь у вас есть два узла, которые выглядят одинаково, но выполняют разную работу. Оба используют один и тот же внутренний код (логику Subflow), но первый будет отправлять Modbus-запросы устройству с адресом 10, а второй — устройству с адресом 11.

    Если вам понадобится изменить логику обработки ответа от Modbus-устройств (например, добавить валидацию или логирование), вы сделаете это в одном месте — внутри шаблона Subflow `SCN-MODBUS-READER`. Изменения автоматически применятся к обоим экземплярам. Это и есть принцип DRY (Don't Repeat Yourself) в действии.

    JSON-представление потока будет выглядеть примерно так (фрагмент):

    [
    

    {

    "id": "instance_1",

    "type": "subflow:SCN-MODBUS-READER",

    "name": "Температура в гостиной",

    "env": [

    {

    "name": "MODBUS_SLAVE_ID",

    "value": "10",

    "type": "num"

    }

    ]

    },

    {

    "id": "instance_2",

    "type": "subflow:SCN-MODBUS-READER",

    "name": "CO2 в гостиной",

    "env": [

    {

    "name": "MODBUS_SLAVE_ID",

    "value": "11",

    "type": "num"

    }

    ]

    }

    ]

    Это наглядно демонстрирует, как для каждого экземпляра (`instance_1`, `instance_2`) задается свое значение переменной `MODBUS_SLAVE_ID`.

    ---

    Продвинутый кейс: Универсальный обработчик данных с RS-485

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

    Задача: Создать Subflow `FLOW-INTEG-MODBUS-GETTER-001`, который будет периодически опрашивать Modbus-устройство, обрабатывать ответ и выдавать данные в стандартном формате "контракта сообщения". Шаг 1: Проектирование и определение свойств

    Нам нужно вынести наружу все параметры, которые могут меняться от устройства к устройству.

    Шаг 2: Создание логики внутри Subflow

    Внутренняя структура Subflow будет выглядеть так:

    `[Inject]` -> `[Modbus-Getter]` -> `[Function: Parse & Validate]` -> `[Switch: Check Valid]` -> `[mqtt out]`

    |

    +-----> `(Выход 2: Ошибка)`

  • Узел `Inject`: Этот узел будет запускать опрос. Настроим его так, чтобы он срабатывал при старте и повторялся с интервалом.
  • * `Inject once after 0.1 seconds`

    * `Repeat`: `interval`

    * `every`: `${POLL_INTERVAL_SEC}` `seconds`. (Да, здесь тоже можно использовать переменные!)

  • Узел `Modbus-Getter`: Сердце нашего Subflow.
  • * `Server`: Выбираем наш заранее настроенный узел конфигурации для шины RS-485.

    * `Unit-ID`: `${MODBUS_SLAVE_ID}`

    * `FC`: `FC 4: Read Input Registers` (для примера)

    * `Address`: `${REGISTER_ADDRESS}`

    * `Quantity`: `${REGISTER_COUNT}`

    > ⚠️ Внимание: Убедитесь, что тип данных, установленный для свойства (например, 'number'), соответствует тому, что ожидает нода внутри Subflow. Передача 'string' вместо 'number' в поле `Unit-ID` или `Address` узла Modbus вызовет ошибку `Invalid MODBUS-RTU frame` или аналогичную ошибку валидации.

  • Узел `Function: Parse & Validate`: Здесь происходит обработка ответа.
  •     // Получаем ID устройства для логирования

    const deviceId = env.get('MODBUS_SLAVE_ID');

    // Проверяем, что ответ от Modbus-узла корректен

    if (!msg.payload || !msg.payload.data) {

    node.error(`Modbus error or timeout for device ${deviceId}`, msg);

    node.status({ fill: "red", shape: "dot", text: "Error/Timeout" });

    return null; // Прерываем поток

    }

    // Пример парсинга: просто берем первое значение

    // В реальном проекте здесь может быть сложная логика

    // для работы с 32-битными числами, float и т.д.

    const rawValue = msg.payload.data[0];

    // Валидация (пример)

    if (rawValue > 6000) { // Например, температура * 100 не может быть > 60°C

    node.warn(`Invalid value ${rawValue} from device ${deviceId}`);

    node.status({ fill: "yellow", shape: "ring", text: `Invalid: ${rawValue}`});

    msg.error = `Invalid value received: ${rawValue}`;

    return [null, msg]; // Отправляем на второй выход (ошибка)

    }

    // Формируем сообщение по "Контракту сообщения"

    msg.payload = {

    value: rawValue / 100, // Пример для температуры

    source: `modbus-device-${deviceId}`,

    ts: Date.now(),

    unit: '°C'

    };

    // Устанавливаем топик для публикации

    msg.topic = env.get('OUTPUT_TOPIC');

    node.status({ fill: "green", shape: "dot", text: `OK: ${msg.payload.value}°C` });

    return [msg, null]; // Отправляем на первый выход (успех)

  • Узел `Switch: Check Valid`: Направляет поток в зависимости от результата в `Function`. Имеет два выхода.
  • * `msg.payload` `is not null` -> Выход 1

    * `otherwise` -> Выход 2

  • Узел `mqtt out`: Подключен к выходу 1.
  • * `Topic`: Оставляем пустым, так как топик уже установлен в `msg.topic`.

    * `Server`: Выбираем наш MQTT-брокер.

  • Выходы Subflow:
  • * `Output 1`: Успешно обработанные данные.

    * `Output 2`: Сообщения об ошибках валидации.

    Шаг 3: Использование

    Теперь на основной вкладке мы можем разместить три экземпляра `FLOW-INTEG-MODBUS-GETTER-001` для опроса трех разных датчиков на одной шине RS-485:

    * `MODBUS_SLAVE_ID`: `21`

    * `REGISTER_ADDRESS`: `100`

    * `REGISTER_COUNT`: `1`

    * `POLL_INTERVAL_SEC`: `30`

    * `OUTPUT_TOPIC`: `telemetry/room1/temperature`

    * `MODBUS_SLAVE_ID`: `21`

    * `REGISTER_ADDRESS`: `101`

    * `REGISTER_COUNT`: `1`

    * `POLL_INTERVAL_SEC`: `30`

    * `OUTPUT_TOPIC`: `telemetry/room1/humidity`

    * `MODBUS_SLAVE_ID`: `22`

    * `REGISTER_ADDRESS`: `0`

    * `REGISTER_COUNT`: `1`

    * `POLL_INTERVAL_SEC`: `60`

    * `OUTPUT_TOPIC`: `telemetry/room1/co2`

    Мы создали мощный, универсальный и легко масштабируемый инструмент, избежав громоздкого дублирования потоков.

    ---

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

    Свойства Subflow — это один из самых мощных инструментов в арсенале профессионального разработчика Node-RED. Они позволяют перейти от создания одноразовых потоков к проектированию библиотеки переиспользуемых, параметризуемых и надежных компонентов автоматизации. Это прямое воплощение принципа DRY (Don't Repeat Yourself), который гласит: «Не повторяйте себя».

    Лучшие практики:

  • Документируйте свойства: В описании самого Subflow (вкладка `Appearance` -> `Description`) всегда подробно описывайте каждое свойство: его назначение, тип данных и возможные значения. Другой инженер (или вы сами через полгода) скажет вам спасибо.
  • Задавайте разумные значения по умолчанию: Значение по умолчанию должно обеспечивать работоспособность Subflow в самом распространенном сценарии. Это упрощает его использование.
  • Используйте единый стандарт именования: Придерживайтесь `SNAKE_CASE_UPPER` (например, `DEVICE_ID`, `MQTT_TOPIC`) для имен свойств. Это визуально отделяет их от других переменных в коде.
  • Выносите в свойства только то, что действительно нужно: Не стоит параметризировать каждую мелочь. Выносите только те данные, которые будут меняться от экземпляра к экземпляру: адреса, ID, топики, пины GPIO, API-ключи.
  • Разделяйте конфигурацию и логику: Свойства Subflow — это конфигурация. Логика находится внутри. Никогда не смешивайте эти понятия. Логика не должна зависеть от конкретного значения свойства (например, `if (env.get('DEVICE_ID') === '10') { ... }`), она должна быть универсальной.
  • Когда использовать свойства Subflow, а когда — контекст?

    | Сценарий | Лучший инструмент | Почему? |

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

    | Настройка ID Modbus-устройства для датчика | Свойство Subflow | Это статическая конфигурация, которая задается один раз при проектировании и не меняется во время работы. |

    | Хранение последнего известного состояния реле | Контекст потока | Это динамическое состояние, которое меняется в реальном времени. Оно должно сохраняться между вызовами потока. |

    | API-ключ для доступа к погодному сервису | Свойство Subflow | Это секрет/конфигурация. Его удобно задавать для каждого экземпляра, если ключи разные. |

    | Счетчик срабатываний датчика движения | Контекст узла/потока | Это динамическое, изменяемое в runtime значение. |

    Что дальше

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

    В следующем уроке мы перейдем к не менее важной теме — управлению зависимостями проекта и использованию внешних npm-модулей, что позволит расширить стандартные возможности Node-RED и интегрировать в наши потоки практически любую существующую библиотеку Node.js.