ГлавнаяАкадемияNode-RED: установка, flows, msg/JSON, отладка → Нода `Template`: форматирование строк и генерация текста

Нода `Template`: форматирование строк и генерация текста

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

Нода Template: назначение и основы синтаксиса

В арсенале инженера по автоматизации уже есть мощные инструменты для манипуляции данными, такие как ноды `Change` и `Function`. Нода `Change` идеально подходит для прямолинейных операций: установить, изменить или удалить свойство сообщения. Нода `Function` предоставляет безграничную гибкость JavaScript для сложных вычислений и алгоритмов. Однако существует класс задач, где оба этих инструмента оказываются либо избыточными, либо неудобными: формирование сложных текстовых строк на основе данных из сообщения.

Именно для этого и предназначена нода `Template`.

> 💡 Подсказка: Синтаксис, используемый в ноде `Template`, называется Mustache. Это популярный и легковесный стандарт для работы с шаблонами, который применяется не только в Node-RED, но и во многих веб-фреймворках и системах генерации документов. Освоив его здесь, вы получите универсальный навык.

Назначение ноды `Template` — генерация форматированного вывода (будь то обычный текст, HTML, XML, JSON или YAML) путем подстановки значений из объекта `msg` в заранее определенный шаблон. Она действует как "почтовый шаблон", где вы один раз создаете структуру, а Node-RED автоматически заполняет "пробелы" данными из каждого проходящего сообщения.

Сравнение с нодой `Change`

Давайте наглядно сравним, когда использовать `Change`, а когда `Template`.

| Задача | Рекомендуемый инструмент | Пояснение |

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

| Установить `msg.payload` в значение `true`. | `Change` | Простая, атомарная операция. |

| Переместить `msg.data.value` в `msg.payload`.| `Change` | Стандартная операция перемещения свойства. |

| Сформировать строку: "Датчик `sensor-01` передал значение `25.5`." | `Template` | Требуется конкатенация строк и подстановка нескольких значений. В `Change` это потребовало бы сложного JSONata выражения, а в `Template` это интуитивно понятно. |

| Создать JSON-структуру для API. | `Template` | Визуально и структурно проще создать шаблон JSON, чем собирать его по частям в ноде `Function` или `Change`. |

Основы синтаксиса Mustache

В основе Mustache лежит всего одна ключевая конструкция: двойные фигурные скобки `{{ }}`. Все, что заключено в эти скобки, рассматривается как имя свойства, которое нужно найти в объекте `msg` и подставить на это место.

Рассмотрим базовый пример. Предположим, контроллер HI считывает данные с датчика температуры 1-Wire, и после обработки нода `function` формирует сообщение согласно "Контракту сообщения":

{

"payload": {

"value": 23.7,

"source": "28-01234567abcd",

"ts": 1678886400000,

"unit": "°C"

},

"topic": "telemetry/living_room/temperature"

}

Наша задача — сформировать простое текстовое уведомление для отправки, например, в Telegram.

  • Разместите на поле ноду `Template`.
  • Откройте ее настройки.
  • Поле Property (Свойство) по умолчанию установлено в `msg.payload`. Это означает, что результат работы шаблона заменит текущее значение `msg.payload`.
  • В поле Template (Шаблон) введем следующий текст:
  • Температура в гостиной: {{payload.value}} {{payload.unit}}.
    

    Данные получены с датчика {{payload.source}}.

    Когда сообщение пройдет через эту ноду, ее `msg.payload` на выходе станет строкой:

    Температура в гостиной: 23.7 °C.
    

    Данные получены с датчика 28-01234567abcd.

    Обратите внимание, как мы используем dot-нотацию (`payload.value`) для доступа к вложенным свойствам, точно так же, как в JavaScript. Mustache по умолчанию ищет свойства в объекте `msg`, поэтому `{{payload.value}}` — это то же самое, что и `{{msg.payload.value}}`.

    ---

    Работа со вложенными объектами и массивами

    Современные системы автоматизации редко оперируют простыми значениями. Чаще всего мы имеем дело со сложными JSON-объектами, приходящими от мультисенсоров или внешних API. Нода `Template` превосходно справляется с разбором таких структур.

    > ⚠️ Внимание: При работе с глубоко вложенными структурами убедитесь, что все промежуточные свойства существуют. Обращение к несуществующему свойству (например, `msg.payload.data.value` при отсутствии `data`) не вызовет ошибку в Node-RED. Вместо этого Mustache просто подставит пустую строку. Это может затруднить отладку, так как поток будет работать "молча", но результат будет некорректным. Всегда проверяйте структуру входящего `msg` в панели отладки.

    Разбор JSON-объекта от датчика

    Представим, что у нас есть Modbus-мультисенсор, который отдает температуру, влажность и уровень CO2. После опроса и первичной обработки в ноде `function` мы получаем следующее сообщение:

    {
    

    "payload": {

    "temperature": 24.1,

    "humidity": 48,

    "co2": 850

    },

    "meta": {

    "room": "Конференц-зал 'Москва'",

    "modbus_id": 15

    }

    }

    Мы хотим сгенерировать HTML-отчет для отображения на панели управления (Node-RED Dashboard).

    В ноде `Template` мы можем написать такой шаблон:

    Отчет по микроклимату

    Помещение: {{meta.room}} (Modbus ID: {{meta.modbus_id}})

      • Температура: {{payload.temperature}} °C
      • Влажность: {{payload.humidity}} %
      • Уровень CO2: {{payload.co2}} ppm

    На выходе ноды мы получим готовый HTML-код в `msg.payload`, который можно напрямую передать в узел `ui_template` для отображения.

    Итерация по массивам

    Одна из самых мощных возможностей Mustache — это работа с массивами. Для этого используются так называемые секции. Секция начинается с тега `{{#имя_массива}}` и заканчивается `{{/имя_массива}}`. Весь контент внутри этой секции будет повторен для каждого элемента массива. Внутри секции все теги `{{...}}` будут ссылаться на свойства текущего элемента массива.

    Практический кейс: генерация списка устройств из API

    Предположим, мы делаем запрос к API нашего контроллера HI, чтобы получить список всех подключенных реле и их состояний. API возвращает массив объектов:

    {
    

    "payload": [

    { "id": "RELAY-01", "name": "Освещение, главный зал", "state": true },

    { "id": "RELAY-02", "name": "Розетки, кухонная зона", "state": false },

    { "id": "RELAY-03", "name": "Привод штор, левое окно", "state": true },

    { "id": "RELAY-04", "name": "Система вентиляции", "state": false }

    ]

    }

    Наша задача — создать читаемый список.

    Шаблон в ноде `Template`:

    Отчет по состоянию исполнительных устройств:
    

    {{#payload}}

    • Устройство: {{name}} (ID: {{id}}). Состояние: {{state}}.
    {{/payload}}

    Проверено: {{#$localTimestamp}}{{.}}{{/$localTimestamp}}

    > ℹ️ Информация: Здесь мы использовали специальный тег `{{#$localTimestamp}}...{{/$localTimestamp}}`, предоставляемый реализацией Mustache в Node-RED для вывода текущей временной метки.

    Результат в `msg.payload`:

    Отчет по состоянию исполнительных устройств:
    
    • Устройство: Освещение, главный зал (ID: RELAY-01). Состояние: true.
    • Устройство: Розетки, кухонная зона (ID: RELAY-02). Состояние: false.
    • Устройство: Привод штор, левое окно (ID: RELAY-03). Состояние: true.
    • Устройство: Система вентиляции (ID: RELAY-04). Состояние: false.
    Проверено: 2023-10-27T10:30:00.123Z

    Этот механизм невероятно полезен для генерации отчетов, списков для email-рассылок или таблиц для веб-интерфейсов.

    ---

    Условная логика и форматирование вывода

    Несмотря на философию "logic-less" (минимум логики), Mustache предоставляет базовые инструменты для условного отображения блоков. Эта функциональность также реализуется через секции `{{#...}}` и их инвертированный вариант `{{^...}}`.

    > 🔗 Связанный материал: Нода `Template` хорошо подходит для простой условной визуализации. Однако для сложной маршрутизации сообщений на основе условий (например, отправка в разные топики MQTT в зависимости от значения) всегда используйте ноду `Switch`, как мы подробно рассмотрели в уроке COURSE-06-M03-L03.

    Правила условного отображения

    Как Mustache решает, показывать блок или нет?

    * Существует.

    * Не равно `null`.

    * Не равно `false`.

    * Не является пустым массивом `[]`.

    * Не равно `0` (ноль).

    Пример: кастомизированные сообщения о статусе

    Вернемся к нашему примеру со списком реле. Вывод `true` или `false` неудобен для пользователя. С помощью условных секций мы можем сделать его гораздо более информативным.

    Используем тот же входящий `msg` с массивом реле.

    Новый, улучшенный шаблон в ноде `Template`:

    
    

    Состояния реле

    {{#payload}}

    {{/payload}}

    УстройствоIDСтатус
    {{name}} {{id}}

    {{#state}}ВКЛЮЧЕНО{{/state}}

    {{^state}}ВЫКЛЮЧЕНО{{/state}}

    Здесь мы применили сразу несколько техник:

  • `{{#payload}}...{{/payload}}` для итерации по каждому реле в массиве.
  • `{{#state}}...{{/state}}` для отображения блока "ВКЛЮЧЕНО", если `state` равно `true`.
  • `{{^state}}...{{/state}}` для отображения блока "ВЫКЛЮЧЕНО", если `state` равно `false`.
  • Результатом будет красивая и понятная HTML-таблица, готовая для вставки в дашборд.

    Ограничения логики

    Крайне важно понимать, что вы не можете делать в Mustache:

    Математические операции: Нельзя написать `{{payload.price 1.2}}`.

    Для всех этих задач необходимо подготовить данные перед тем, как они попадут в ноду `Template`. Классический паттерн Node-RED выглядит так:

    `[Источник данных]` -> `[Function или Change для подготовки]` -> `[Template для форматирования]` -> `[Получатель]`

    Например, если вам нужно отобразить разный текст для температур выше и ниже 20°C, вы сначала используете ноду `Switch` для разделения потока на два, или ноду `Change` / `Function` для добавления нового флага, например, `msg.is_warm = (msg.payload.temperature > 20)`. А уже в ноде `Template` вы будете использовать простую проверку `{{#is_warm}}...{{/is_warm}}`.

    ---

    Пример: генерация JSON для API-запросов

    Нода `Template` — это не только про текст для людей. Это еще и мощнейший инструмент для формирования структурированных данных для машин, например, JSON-объектов для отправки в API.

    Ключевой момент при генерации JSON — это выбор правильного формата вывода в настройках ноды.

    Использование `Parsed JSON` — предпочтительная практика, так как это гарантирует синтаксическую корректность вашего JSON перед отправкой.

    Кейс: управление яркостью светильника DALI

    Представим, что на объекте (например, в офисе) используется освещение по протоколу DALI. Управление осуществляется через шлюз, который принимает команды по MQTT в строго определенном JSON-формате.

    Задача: создать поток, который принимает простое значение яркости (например, из слайдера на дашборде) и формирует из него правильный JSON-запрос.

  • Входное сообщение (например, от `ui_slider`):
  •     {

    "payload": 80, // Яркость в процентах

    "topic": "dali/office/group_1"

    }

  • Целевой формат JSON для MQTT-API шлюза:
  •     {

    "command": "set_brightness_percent",

    "target_group": "group_1",

    "value": 80,

    "fade_time_ms": 1000,

    "request_id": "req-1698399512345"

    }

  • Настройка ноды `Template`:
  • * Property: `msg.payload`

    * Format: `Parsed JSON`

    * Syntax: `Mustache`

    * Template:

        {

    "command": "set_brightness_percent",

    "target_group": "{{topic}}".split('/')[2],

    "value": {{payload}},

    "fade_time_ms": 1000,

    "request_id": "req-{{#$millis}}{{.}}{{/$millis}}"

    }

    > ℹ️ В примере выше для извлечения `group_1` из топика `dali/office/group_1` используется JSONata, а не Mustache. Чтобы оставаться в рамках Mustache, корректнее было бы подготовить это поле заранее. Однако современная версия ноды `Template` позволяет использовать синтаксис JSONata даже в режиме Mustache для простых выражений. Мы рассмотрим JSONata подробно в следующем уроке. А пока предположим, что мы передаем ID группы в отдельном свойстве `msg.target_group`, подготовленном нодой `Change`.

    Корректный шаблон для чистого Mustache (с предварительной подготовкой данных):

    Входящее сообщение после ноды `Change`:

        {

    "payload": 80,

    "topic": "dali/office/group_1",

    "target_group": "group_1"

    }

    Шаблон:

        {

    "command": "set_brightness_percent",

    "target_group": "{{target_group}}",

    "value": {{payload}},

    "fade_time_ms": 1000,

    "request_id": "req-{{#$millis}}{{.}}{{/$millis}}"

    }

    Важные моменты при генерации JSON:

    На выходе из ноды `Template` мы получим готовый JavaScript-объект в `msg.payload`, который можно сразу направить в ноду `mqtt out` для отправки команды на DALI-шлюз.

    ---

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

    Нода `Template` — это незаменимый инструмент в арсенале любого инженера автоматизации, работающего с Node-RED. Она заполняет нишу между простыми манипуляциями в `Change` и сложной логикой в `Function`, предоставляя элегантный способ для решения задач форматирования.

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

    Выбор правильного инструмента

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

    | Если вам нужно... | Используйте... | Пример |

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

    | ...установить, заменить, переместить или удалить свойство. | Нода `Change` | Установить `msg.payload` в `true`. |

    | ...создать строку, смешивая статический текст со значениями из `msg`.| Нода `Template` | `"Температура: {{payload.temp}}°C."` |

    | ...создать HTML или JSON структуру на основе `msg`. | Нода `Template` | Генерация HTML-таблицы или JSON для API. |

    | ...выполнить математические расчеты. | Нода `Function` | `msg.payload = msg.payload * 1.8 + 32;` |

    | ...применить сложную условную логику (`if/else if/else`, `switch`). | Нода `Function` | Проверка нескольких условий перед принятием решения. |

    | ...разделить поток сообщений на несколько веток по условию. | Нода `Switch` | Отправить сообщение в разные топики MQTT в зависимости от его содержимого. |

    Следование этому простому правилу сделает ваши потоки более читаемыми, эффективными и простыми в поддержке.

    Что дальше?

    В этом уроке мы освоили синтаксис Mustache для форматирования данных. Однако в настройках ноды `Template`, а также в `Change`, вы могли видеть опцию "JSONata". Это еще более мощный язык запросов и трансформации JSON, который стирает многие границы "logic-less" подхода Mustache. В следующем уроке, COURSE-06-M03-L05, мы глубоко погрузимся в мир JSONata и научимся выполнять сложные преобразования данных одной строкой кода прямо в настройках нод.