Переменные окружения (Environment Variables)
Введение в переменные окружения
В профессиональной разработке и эксплуатации систем автоматизации крайне важно отделять конфигурацию от логики. Логика — это то, что делает ваша система (например, включает свет при обнаружении движения). Конфигурация — это то, с чем она работает (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 на объекте и вручную изменять IP-адрес в каждом узле `mqtt-in` и `mqtt-out`. Это долго, чревато ошибками и требует вмешательства в отлаженную логику.
- Правильный подход: Использовать в узлах переменную окружения, например, `${HI_MQTT_BROKER_IP}`. На тестовом стенде этой переменной присваивается значение `192.168.88.10`, а на объекте заказчика — `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/
sudo nano /etc/systemd/system/nodered.service.d/override.conf
Способ А: Директива `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
После сохранения файла `override.conf` необходимо сообщить `systemd` об изменениях и перезапустить сервис Node-RED, чтобы он запустился с новым окружением.
sudo systemctl daemon-reload
sudo systemctl restart nodered
Теперь процесс Node-RED запущен с указанными вами переменными окружения, и они готовы к использованию в потоках.
---
Использование переменных в узлах Node-RED
После настройки переменных на уровне ОС, их можно использовать для параметризации узлов, что делает потоки гибкими и портируемыми.
Использование в полях настроек узлов
Большинство стандартных узлов, требующих ввода адресов, портов или других конфигурационных параметров, поддерживают синтаксис `${VAR_NAME}`.
Пример: Настройка узла `mqtt in`
Описание изображения для ясности: в диалоговом окне настроек 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-брокеру.
Этот же подход работает для многих других узлов:
- `http request`: В поле URL можно написать `http://${API_HOST}/endpoint`.
- `tcp request`: В полях Host и Port.
- `e-mail`: В полях SMTP Server и Port.
Доступ к переменным из узла `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 выполняет перед* тем, как передать конфигурацию узлу. Он работает только в тех полях, где это явно поддержано разработчиком узла.
- Объект `process.env` — это прямой доступ к среде выполнения Node.js внутри вашего JavaScript-кода. Он позволяет использовать переменные окружения для любой логики: формирования строк, условных операторов (`if (process.env.HI_LOG_LEVEL === 'debug') { ... }`), математических вычислений и т.д.
---
Переменные окружения и TypedInput
TypedInput — это стандартный виджет пользовательского интерфейса в Node-RED, который вы видите во многих узлах (например, `Change`, `Inject`, `Switch`). Он позволяет не просто ввести значение, но и явно указать его тип: строка, число, JSON, `msg`, `flow`, `global` и, что нас интересует, переменная окружения (env).Использование типа `env` в TypedInput является более современным и надежным способом работы с переменными окружения по сравнению с ручным вводом синтаксиса `${...}`.
Преимущества TypedInput типа 'env'
Практическое применение
Представим, что мы хотим установить `msg.topic` равным значению переменной окружения `HI_TARGET_TOPIC`.
Способ 1 (старый): Узел `Change`, тип "String"- Установить `msg.topic`
- В значение `"a-z"` (String) `▼`
- Ввести: `${HI_TARGET_TOPIC}`
- Установить `msg.topic`
- В значение `env` (Environment Variable) `▼`
- Ввести: `HI_TARGET_TOPIC` (без `${}`)
В обоих случаях результат будет одинаковым: `msg.topic` получит строковое значение из переменной окружения `HI_TARGET_TOPIC`. Однако второй способ более структурированный и "самодокументируемый".
Этот же подход можно использовать для динамического формирования полезной нагрузки. Например, в узле `Inject` можно настроить `msg.payload` на получение значения из переменной окружения `DEFAULT_PAYLOAD`. Это удобно для сценариев тестирования и отладки.
---
Комбинация с переменными в Subflows
Один из самых мощных паттернов для создания по-настоящему переиспользуемых и профессиональных компонентов — это комбинация Subflows (подпотоков) с переменными окружения.
> 🔗 Связанный материал: Данный раздел тесно связан с концепциями, изложенными в уроке COURSE-06-M06-L04 "Настройка свойств Subflow (переменные окружения)".
Как мы рассматривали ранее, Subflow может иметь свои собственные свойства (properties), которые действуют как переменные окружения, но локальные для этого Subflow. При этом для каждого такого свойства можно задать значение по умолчанию. Здесь и открывается возможность интеграции с глобальными переменными окружения.
Паттерн: Глобальное значение по умолчанию с локальным переопределениемИспользуйте переменные окружения для задания значений по умолчанию в настройках свойств Subflow.
Пример: Универсальный Subflow для отправки уведомлений в Telegram
Environment="HI_DEFAULT_TELEGRAM_CHAT_ID=-1234567890"
Не забудьте перезапустить сервис Node-RED.
* Создайте новый Subflow (например, `Notifier-Telegram`).
* Перейдите в `Properties` -> `Edit subflow properties`.
* Добавьте новое свойство:
* Name: `CHAT_ID`
* Label: `Chat ID`
* Type: `text`
* Default value: `${HI_DEFAULT_TELEGRAM_CHAT_ID}`
Теперь рассмотрим, как это работает на практике:
- Сценарий 1: Использование по умолчанию.
- Сценарий 2: Локальное переопределение.
Этот подход позволяет создавать универсальные функциональные блоки, которые легко настраиваются под 90% случаев через глобальные переменные окружения, но при этом сохраняют гибкость для особых случаев, где требуется ручная, точечная настройка.
---
Итоги и лучшие практики
Переменные окружения — это не просто удобство, а стандартный промышленный подход для отделения конфигурации от логики ваших потоков. Внедрение этой парадигмы в ваши проекты значительно повышает их качество, надежность и поддерживаемость.
📋 Ключевые понятия:
- Переменные окружения: Пары "ключ-значение", задаваемые на уровне ОС для конфигурации приложения.
- Конфигурация vs Состояние: Переменные окружения хранят статическую конфигурацию, контекстные переменные — динамическое состояние.
- Портируемость: Возможность легко переносить потоки между разными средами (тест, продакшен) без изменения их логики.
- systemd `override.conf`: Рекомендованный метод постоянной настройки переменных для сервиса Node-RED в Linux.
- Синтаксис `${...}`: Механизм подстановки значений в полях настроек узлов.
- `process.env`: Объект Node.js для доступа к переменным окружения из кода в узле `Function`.
- TypedInput: Современный UI-виджет для явного указания типа данных, включая `env`.
Лучшие практики (Best Practices)
* IP-адреса и хостнеймы (MQTT, Modbus TCP, HTTP API).
* Порты сервисов.
* Уникальные идентификаторы контроллера или объекта (`HI_CONTROLLER_ID`).
* Пути к файлам или директориям, которые могут отличаться на разных системах.
* Режимы работы (`production`, `development`).
* `HI_MQTT_HOST`
* `HI_MYSQL_DATABASE`
* `HI_ALERT_EMAIL_TO`
Что дальше
В следующем уроке мы изучим еще один мощный инструмент для организации кода — глобальные узлы конфигурации (Global Configuration Nodes). Мы научимся создавать централизованные точки подключения к сервисам, таким как базы данных или API, и использовать их во всем проекте, что еще больше повысит модульность и управляемость наших систем автоматизации.