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

Переменные окружения (Environment Variables)

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

Введение в переменные окружения

В профессиональной разработке и эксплуатации систем автоматизации крайне важно отделять конфигурацию от логики. Логика — это то, что делает ваша система (например, включает свет при обнаружении движения). Конфигурация — это то, с чем она работает (IP-адрес MQTT-брокера, ID Modbus-устройства, порт почтового сервера). Смешивание этих двух сущностей приводит к системам, которые сложно развертывать, обновлять и поддерживать.

Переменные окружения (Environment Variables) — это стандартный механизм, позволяющий вынести конфигурацию за пределы кода приложения (в нашем случае, за пределы потоков Node-RED). Они представляют собой именованные значения, или пары «ключ-значение», которые задаются на уровне операционной системы контроллера HI (Linux Debian) и становятся доступными для процесса Node-RED во время его работы.

> 💡 Подсказка: Контекстные переменные (`flow`, `global`) хранят динамическое состояние внутри потока (например, счетчики срабатываний, флаги состояний, временные данные). Переменные окружения хранят статическую конфигурацию для потока (IP-адреса, порты, идентификаторы устройств, режимы работы).

Ключевое преимущество использования переменных окружения — портируемость (переносимость) проектов. Представьте, что вы разработали и отладили проект для умной квартиры на стенде в офисе. IP-адрес вашего тестового MQTT-брокера — `192.168.88.10`. На объекте у заказчика IP-адрес брокера будет другим, например, `192.168.1.5`.

Отличие от других типов данных

Важно четко понимать место переменных окружения в иерархии данных Node-RED.

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

| :--- | :--- | :--- | :--- |

| Свойство `msg` | Один поток, от узла к узлу | От начала до конца потока (очень короткий) | Передача динамических данных внутри одного выполнения потока. |

| Контекст `flow` | Все узлы на одной вкладке | Пока Node-RED запущен | Хранение состояния, общего для группы связанных потоков (например, состояние FSM климат-контроля). |

| Контекст `global` | Все узлы во всем проекте | Пока Node-RED запущен | Хранение глобального состояния, доступного отовсюду (использовать с осторожностью!). |

| Credential | Защищенное хранилище | Постоянный, шифрованный | Хранение секретов: паролей, ключей API, токенов. |

| Переменная окружения| Весь процесс Node-RED | Постоянный, задается извне | Хранение статической конфигурации: адресов, портов, идентификаторов, режимов. |

Для доступа к переменным окружения в большинстве полей настройки узлов Node-RED используется специальный синтаксис: `${ENV_VAR_NAME}`. При запуске потока Node-RED автоматически подставляет на это место значение соответствующей переменной из окружения операционной системы.

---

Настройка переменных в среде контроллера HI (Linux)

Переменные окружения должны быть заданы в той среде, где запускается сервис Node-RED. На контроллерах HI под управлением Debian для этого используется система инициализации systemd. Этот метод является наиболее правильным и надежным, так как он гарантирует, что переменные будут доступны сервису после каждой перезагрузки контроллера.

> ⚠️ Внимание: Никогда не храните критически важные данные (пароли, ключи API, токены) в файлах переменных окружения в открытом виде. Для этих целей всегда используйте `Credentials Service`, как было рассмотрено в уроке COURSE-06-M06-L06. Переменные окружения предназначены для несекретной конфигурации.

Временная установка для отладки

Иногда для быстрой проверки гипотезы нужно задать переменную на время текущей сессии. Это можно сделать прямо в командной строке контроллера через SSH, используя команду `export`:

export HI_MQTT_BROKER_IP="192.168.1.5"

export HI_CONTROLLER_ID="SITE-01-FLOOR-03"

Эти переменные будут существовать только в рамках текущей SSH-сессии и пропадут после ее закрытия или перезагрузки контроллера. Сервис Node-RED, запущенный через `systemd`, их не увидит.

Постоянная настройка через systemd (рекомендованный метод)

Чтобы переменные окружения были доступны сервису Node-RED постоянно, необходимо модифицировать его `unit`-файл. Лучшая практика — не редактировать основной файл сервиса (`/lib/systemd/system/nodered.service`), а создать для него файл переопределения (`override`). Это гарантирует, что ваши настройки не будут затерты при обновлении пакета Node-RED.

  • Создайте директорию для файла переопределения:
  •     sudo mkdir -p /etc/systemd/system/nodered.service.d/

  • Создайте и откройте файл `override.conf` в текстовом редакторе, например, `nano`:
  •     sudo nano /etc/systemd/system/nodered.service.d/override.conf

  • Добавьте содержимое в файл. Существует два способа: директива `Environment` для отдельных переменных и `EnvironmentFile` для загрузки из файла.
  • Способ А: Директива `Environment` (для нескольких переменных)

    Этот способ удобен, если у вас 2-5 переменных.

        [Service]

    # Задаем переменные окружения напрямую

    Environment="HI_MQTT_BROKER_IP=192.168.1.5"

    Environment="HI_MQTT_PORT=1883"

    Environment="HI_CONTROLLER_ID=APT-101"

    Environment="HI_LOG_LEVEL=info"

    Способ Б: Директива `EnvironmentFile` (для большого количества переменных)

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

    Создайте файл `/opt/hi-academy/nodered.env`:

        sudo mkdir -p /opt/hi-academy

    sudo nano /opt/hi-academy/nodered.env

    Содержимое файла `.env`:

        # Конфигурация для проекта "Умный офис"

    HI_MQTT_BROKER_IP=192.168.1.5

    HI_MYSQL_HOST=localhost

    HI_MYSQL_USER=nodered_user

    HI_DALI_GATEWAY=192.168.1.20

    HI_CONTROLLER_ID=OFFICE-3RD-FLOOR

    Теперь в файле `override.conf` укажите путь к этому файлу. Обратите внимание на `-` перед путем — он говорит systemd не считать ошибкой, если файл не найден.

        [Service]

    EnvironmentFile=-/opt/hi-academy/nodered.env

  • Перезагрузите конфигурацию `systemd` и перезапустите сервис Node-RED:
  • После сохранения файла `override.conf` необходимо сообщить `systemd` об изменениях и перезапустить сервис Node-RED, чтобы он запустился с новым окружением.

        sudo systemctl daemon-reload

    sudo systemctl restart nodered

    Теперь процесс Node-RED запущен с указанными вами переменными окружения, и они готовы к использованию в потоках.

    ---

    Использование переменных в узлах Node-RED

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

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

    Большинство стандартных узлов, требующих ввода адресов, портов или других конфигурационных параметров, поддерживают синтаксис `${VAR_NAME}`.

    Пример: Настройка узла `mqtt in`
  • Откройте узел `mqtt in` или `mqtt out`.
  • Нажмите на кнопку редактирования сервера (карандаш).
  • В поле «Сервер» введите `${HI_MQTT_BROKER_IP}`.
  • В поле «Порт» введите `${HI_MQTT_PORT}`.
  • Пример использования переменных окружения в настройках MQTT-брокера Описание изображения для ясности: в диалоговом окне настроек MQTT-сервера в поле "Server" вписан текст `${HI_MQTT_BROKER_IP}`, а в поле "Port" — `${HI_MQTT_PORT}`.

    Теперь, при развертывании потока, Node-RED не будет использовать строки "`${HI_MQTT_BROKER_IP}`" и "`${HI_MQTT_PORT}`" буквально. Вместо этого он обратится к операционной системе, получит значения этих переменных (`192.168.1.5` и `1883` из нашего предыдущего примера) и использует их для подключения к MQTT-брокеру.

    Этот же подход работает для многих других узлов:

    Доступ к переменным из узла `Function`

    Внутри узла `Function` вы получаете доступ ко всей мощи JavaScript и среды выполнения Node.js. Переменные окружения доступны через глобальный объект `process.env`.

    Это объект, где ключами являются имена переменных, а значениями — их строковые представления.

    > ℹ️ Информация: Все значения, полученные из `process.env`, являются строками. Если ваша переменная окружения содержит число (например, `HI_MQTT_PORT=1883`), при использовании в `Function` его необходимо явно преобразовать в числовой тип с помощью `parseInt()` или `parseFloat()`.

    Пример: Добавление ID контроллера в топик MQTT

    Предположим, у нас есть переменная окружения `HI_CONTROLLER_ID=APT-101`. Мы хотим, чтобы все телеметрические данные с этого контроллера публиковались в ветку MQTT, которая включает этот ID.

    // Получаем ID контроллера из переменных окружения.
    

    // Используем оператор || для задания значения по умолчанию,

    // если переменная вдруг не определена.

    const controllerId = process.env.HI_CONTROLLER_ID || "unknown_controller";

    // Изначально msg.topic был, например, "telemetry/temperature"

    // Мы добавляем префикс с ID контроллера

    msg.topic = `devices/${controllerId}/${msg.topic}`;

    // Пример структуры исходящего сообщения

    // msg.payload = {

    // "value": 22.5,

    // "source": "temp-sensor-livingroom",

    // "ts": 1678886400000,

    // "unit": "°C"

    // };

    // msg.topic теперь будет "devices/APT-101/telemetry/temperature"

    return msg;

    Разница в использовании: Синтаксис `${...}` — это механизм подстановки, который Node-RED выполняет перед* тем, как передать конфигурацию узлу. Он работает только в тех полях, где это явно поддержано разработчиком узла.

    ---

    Переменные окружения и TypedInput

    TypedInput — это стандартный виджет пользовательского интерфейса в Node-RED, который вы видите во многих узлах (например, `Change`, `Inject`, `Switch`). Он позволяет не просто ввести значение, но и явно указать его тип: строка, число, JSON, `msg`, `flow`, `global` и, что нас интересует, переменная окружения (env).

    Использование типа `env` в TypedInput является более современным и надежным способом работы с переменными окружения по сравнению с ручным вводом синтаксиса `${...}`.

    Преимущества TypedInput типа 'env'

  • Читаемость: Когда вы открываете узел, настроенный с помощью TypedInput, вы сразу видите иконку глобуса и метку "env.", что недвусмысленно указывает на использование переменной окружения. Это гораздо нагляднее, чем строка `${...}`, затерянная в текстовом поле.
  • Снижение риска опечаток: Вам не нужно помнить и вручную набирать синтаксис `${}`. Вы просто выбираете тип `env` и вводите имя переменной. Это исключает ошибки вроде `$ {VAR}` или `$(VAR)`.
  • Отсутствие неоднозначности: В некоторых случаях строка может содержать символы `${` как часть своего значения. TypedInput явно разделяет намерение использовать переменную окружения от простого ввода текста.
  • Практическое применение

    Представим, что мы хотим установить `msg.topic` равным значению переменной окружения `HI_TARGET_TOPIC`.

    Способ 1 (старый): Узел `Change`, тип "String" Способ 2 (рекомендованный): Узел `Change`, тип "Environment Variable"

    В обоих случаях результат будет одинаковым: `msg.topic` получит строковое значение из переменной окружения `HI_TARGET_TOPIC`. Однако второй способ более структурированный и "самодокументируемый".

    Этот же подход можно использовать для динамического формирования полезной нагрузки. Например, в узле `Inject` можно настроить `msg.payload` на получение значения из переменной окружения `DEFAULT_PAYLOAD`. Это удобно для сценариев тестирования и отладки.

    ---

    Комбинация с переменными в Subflows

    Один из самых мощных паттернов для создания по-настоящему переиспользуемых и профессиональных компонентов — это комбинация Subflows (подпотоков) с переменными окружения.

    > 🔗 Связанный материал: Данный раздел тесно связан с концепциями, изложенными в уроке COURSE-06-M06-L04 "Настройка свойств Subflow (переменные окружения)".

    Как мы рассматривали ранее, Subflow может иметь свои собственные свойства (properties), которые действуют как переменные окружения, но локальные для этого Subflow. При этом для каждого такого свойства можно задать значение по умолчанию. Здесь и открывается возможность интеграции с глобальными переменными окружения.

    Паттерн: Глобальное значение по умолчанию с локальным переопределением

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

    Пример: Универсальный Subflow для отправки уведомлений в Telegram
  • Задача: Создать Subflow, который принимает текст сообщения на вход и отправляет его в чат Telegram. ID чата может быть разным, но чаще всего это будет один и тот же "дежурный" чат.
  • Настройка переменных окружения: В файле `/etc/systemd/system/nodered.service.d/override.conf` на контроллере зададим ID дежурного чата:
  •     Environment="HI_DEFAULT_TELEGRAM_CHAT_ID=-1234567890"

    Не забудьте перезапустить сервис Node-RED.

  • Создание Subflow:
  • * Создайте новый Subflow (например, `Notifier-Telegram`).

    * Перейдите в `Properties` -> `Edit subflow properties`.

    * Добавьте новое свойство:

    * Name: `CHAT_ID`

    * Label: `Chat ID`

    * Type: `text`

    * Default value: `${HI_DEFAULT_TELEGRAM_CHAT_ID}`

  • Логика внутри Subflow: Внутри Subflow используйте узел `Change` или `Function` для установки `msg.payload.chatId`, используя TypedInput типа "Subflow property" и выбрав `CHAT_ID`.
  • Теперь рассмотрим, как это работает на практике:

    Вы добавляете экземпляр Subflow `Notifier-Telegram` на свой основной поток. Вы не трогаете его настройки. Когда поток выполняется, Subflow "видит", что свойство `Chat ID` не переопределено локально. Он берет значение по умолчанию, которое равно `${HI_DEFAULT_TELEGRAM_CHAT_ID}`. Node-RED подставляет реальное значение `-1234567890`, и уведомление уходит в дежурный чат. В другом потоке вам нужно отправить уведомление в личный чат администратора, а не в дежурный. Вы добавляете еще один экземпляр Subflow `Notifier-Telegram`. Открываете его настройки и в поле `Chat ID` вручную вписываете ID личного чата, например, `987654321`. В этом случае локальное значение имеет приоритет, и Subflow использует его, игнорируя значение по умолчанию.

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

    ---

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

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

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

    Лучшие практики (Best Practices)

  • Используйте для значений, зависящих от окружения. Всегда выносите в переменные окружения такие параметры, как:
  • * IP-адреса и хостнеймы (MQTT, Modbus TCP, HTTP API).

    * Порты сервисов.

    * Уникальные идентификаторы контроллера или объекта (`HI_CONTROLLER_ID`).

    * Пути к файлам или директориям, которые могут отличаться на разных системах.

    * Режимы работы (`production`, `development`).

  • Для секретов всегда используйте Credentials Service. Повторим еще раз: пароли, токены, ключи API не должны находиться в переменных окружения в открытом виде. Храните их только в зашифрованных файлах `flows_cred.json`, используя `Credentials Service`.
  • Придерживайтесь единого стандарта именования. Чтобы избежать хаоса, используйте префиксы и понятные имена в верхнем регистре, разделенные подчеркиванием. Например:
  • * `HI_MQTT_HOST`

    * `HI_MYSQL_DATABASE`

    * `HI_ALERT_EMAIL_TO`

  • Документируйте требуемые переменные. Ваш проект должен быть самодостаточным. В описании главного потока (в узле `Comment`) или в отдельном файле `README.md` в директории проекта перечислите все переменные окружения, которые необходимы для его работы, и приведите примеры их значений. Это сэкономит часы времени тому, кто будет разворачивать ваш проект после вас (или вам самим через полгода).
  • Что дальше

    В следующем уроке мы изучим еще один мощный инструмент для организации кода — глобальные узлы конфигурации (Global Configuration Nodes). Мы научимся создавать централизованные точки подключения к сервисам, таким как базы данных или API, и использовать их во всем проекте, что еще больше повысит модульность и управляемость наших систем автоматизации.