Настройка свойств Subflow (переменные окружения)
Введение в свойства 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 предоставляет инженеру автоматизации три неоспоримых преимущества:
---
Практикум: создание и настройка свойств в редакторе
Рассмотрим пошаговый процесс создания и настройки свойств на примере простого Subflow, который будет добавлять к сообщению префикс.
(Примечание: здесь должна быть иллюстрация интерфейса Node-RED)
(Примечание: здесь должна быть иллюстрация интерфейса Node-RED)
Перед вами появится строка для настройки свойства, содержащая несколько полей:
* Name: Имя переменной. Это тот идентификатор, по которому вы будете обращаться к свойству внутри Subflow. Используйте латиницу и `SNAKE_CASE` (например, `DEVICE_NAME`, `MQTT_TOPIC`).
* Value: Значение по умолчанию. Это значение будет использоваться, если для экземпляра Subflow не будет задано иное.
* Type: Тип данных для значения. Вы можете выбрать `string`, `number`, `boolean` и другие. Это помогает валидировать ввод.
* Input widget: Способ отображения поля для ввода значения в настройках экземпляра. Вы можете выбрать `text input`, `checkbox`, `spinner` (для чисел) и т.д.
* Label: Текстовая метка, которая будет отображаться рядом с полем ввода. Используйте осмысленный текст на русском языке, например, "ID Modbus устройства".
* Placeholder: Текст-подсказка, который отображается внутри пустого поля ввода. Например, "1-247".
* Icon: Вы можете выбрать иконку из библиотеки Font Awesome для визуального выделения свойства.
Давайте создадим свойство с именем `LOG_PREFIX`.
- Name: `LOG_PREFIX`
- Label: `Префикс для лога`
- Type: `string`
- Value (default): `[INFO]`
- Input widget: `text input`
- Placeholder: `Например, [DEVICE-01]`
После добавления всех необходимых свойств, нажмите кнопку Done, а затем Done в окне редактирования Subflow, чтобы сохранить изменения.
> 💡 Подсказка: Используйте осмысленные имена для переменных, которые отражают их назначение, например, `MQTT_TOPIC_PREFIX` или `MODBUS_SLAVE_ID`. Это значительно упрощает конфигурацию экземпляров Subflow в основном потоке. Избегайте общих имен вроде `value1` или `param2`.
---
Использование переменных окружения внутри Subflow
После того как свойство определено, его можно использовать практически в любом узле внутри Subflow. Существует два основных способа доступа к значению переменной.
Основной синтаксис: `${ENV_VAR_NAME}`
Этот синтаксис является стандартным для большинства полей ввода в узлах Node-RED и позволяет напрямую подставлять значение переменной.
Применение в узле `Change`:Предположим, мы хотим установить `msg.topic`, используя наше свойство `LOG_PREFIX`.
Теперь, когда сообщение пройдет через этот узел, его `msg.topic` будет установлен в значение, заданное для данного экземпляра Subflow.
Применение в узле `Template`:Этот узел идеально подходит для формирования сложных строк.
Если свойство `LOG_PREFIX` установлено в `[BoilerRoom]`, а `msg.payload` равен `45.5`, то на выходе узла будет строка: `Это сообщение от устройства с префиксом: [BoilerRoom]. Данные: 45.5`.
Применение в узле `mqtt out`:Это один из самых частых сценариев.
Это позволяет одному и тому же 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:
- Датчик температуры с адресом `10`.
- Датчик CO2 с адресом `11`.
(Примечание: здесь должна быть иллюстрация интерфейса Node-RED)
Теперь у вас есть два узла, которые выглядят одинаково, но выполняют разную работу. Оба используют один и тот же внутренний код (логику 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: Проектирование и определение свойствНам нужно вынести наружу все параметры, которые могут меняться от устройства к устройству.
- `MODBUS_SLAVE_ID`: Slave ID устройства (число, 1-247).
- `REGISTER_ADDRESS`: Адрес первого читаемого регистра (число).
- `REGISTER_COUNT`: Количество читаемых регистров (число).
- `POLL_INTERVAL_SEC`: Интервал опроса в секундах (число).
- `OUTPUT_TOPIC`: MQTT-топик для публикации результата (строка).
Внутренняя структура Subflow будет выглядеть так:
`[Inject]` -> `[Modbus-Getter]` -> `[Function: Parse & Validate]` -> `[Switch: Check Valid]` -> `[mqtt out]`
|
+-----> `(Выход 2: Ошибка)`
* `Inject once after 0.1 seconds`
* `Repeat`: `interval`
* `every`: `${POLL_INTERVAL_SEC}` `seconds`. (Да, здесь тоже можно использовать переменные!)
* `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` или аналогичную ошибку валидации.
// Получаем 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]; // Отправляем на первый выход (успех)
* `msg.payload` `is not null` -> Выход 1
* `otherwise` -> Выход 2
* `Topic`: Оставляем пустым, так как топик уже установлен в `msg.topic`.
* `Server`: Выбираем наш MQTT-брокер.
* `Output 1`: Успешно обработанные данные.
* `Output 2`: Сообщения об ошибках валидации.
Шаг 3: ИспользованиеТеперь на основной вкладке мы можем разместить три экземпляра `FLOW-INTEG-MODBUS-GETTER-001` для опроса трех разных датчиков на одной шине RS-485:
- Экземпляр 1: Температура
* `REGISTER_ADDRESS`: `100`
* `REGISTER_COUNT`: `1`
* `POLL_INTERVAL_SEC`: `30`
* `OUTPUT_TOPIC`: `telemetry/room1/temperature`
- Экземпляр 2: Влажность
* `REGISTER_ADDRESS`: `101`
* `REGISTER_COUNT`: `1`
* `POLL_INTERVAL_SEC`: `30`
* `OUTPUT_TOPIC`: `telemetry/room1/humidity`
- Экземпляр 3: CO2
* `REGISTER_ADDRESS`: `0`
* `REGISTER_COUNT`: `1`
* `POLL_INTERVAL_SEC`: `60`
* `OUTPUT_TOPIC`: `telemetry/room1/co2`
Мы создали мощный, универсальный и легко масштабируемый инструмент, избежав громоздкого дублирования потоков.
---
Итоги и лучшие практики
Свойства Subflow — это один из самых мощных инструментов в арсенале профессионального разработчика Node-RED. Они позволяют перейти от создания одноразовых потоков к проектированию библиотеки переиспользуемых, параметризуемых и надежных компонентов автоматизации. Это прямое воплощение принципа DRY (Don't Repeat Yourself), который гласит: «Не повторяйте себя».
Лучшие практики:
Когда использовать свойства Subflow, а когда — контекст?
| Сценарий | Лучший инструмент | Почему? |
| ---------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
| Настройка ID Modbus-устройства для датчика | Свойство Subflow | Это статическая конфигурация, которая задается один раз при проектировании и не меняется во время работы. |
| Хранение последнего известного состояния реле | Контекст потока | Это динамическое состояние, которое меняется в реальном времени. Оно должно сохраняться между вызовами потока. |
| API-ключ для доступа к погодному сервису | Свойство Subflow | Это секрет/конфигурация. Его удобно задавать для каждого экземпляра, если ключи разные. |
| Счетчик срабатываний датчика движения | Контекст узла/потока | Это динамическое, изменяемое в runtime значение. |
Что дальше
В этом уроке мы освоили мощный механизм параметризации Subflow, который выводит структурирование проектов на новый уровень. Мы научились создавать универсальные компоненты и настраивать их для решения конкретных задач, избегая дублирования кода.
В следующем уроке мы перейдем к не менее важной теме — управлению зависимостями проекта и использованию внешних npm-модулей, что позволит расширить стандартные возможности Node-RED и интегрировать в наши потоки практически любую существующую библиотеку Node.js.