Нода `Template`: форматирование строк и генерация текста
Нода 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.
Температура в гостиной: {{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 решает, показывать блок или нет?
- Секция `{{#property}}...{{/property}}` будет отображена, если `property`:
* Не равно `null`.
* Не равно `false`.
* Не является пустым массивом `[]`.
* Не равно `0` (ноль).
- Инвертированная секция `{{^property}}...{{/property}}` будет отображена во всех обратных случаях (свойство не существует, равно `null`, `false` или является пустым массивом).
Пример: кастомизированные сообщения о статусе
Вернемся к нашему примеру со списком реле. Вывод `true` или `false` неудобен для пользователя. С помощью условных секций мы можем сделать его гораздо более информативным.
Используем тот же входящий `msg` с массивом реле.
Новый, улучшенный шаблон в ноде `Template`:
Состояния реле
Устройство ID Статус
{{#payload}}
{{name}}
{{id}}
{{#state}}ВКЛЮЧЕНО{{/state}}
{{^state}}ВЫКЛЮЧЕНО{{/state}}
{{/payload}}
Здесь мы применили сразу несколько техник:
Результатом будет красивая и понятная HTML-таблица, готовая для вставки в дашборд.
Ограничения логики
Крайне важно понимать, что вы не можете делать в Mustache:
- Сравнения: Нельзя написать `{{#payload.temperature > 30}}`.
- Вызовы функций: Нельзя написать `{{payload.name.toUpperCase()}}`.
Для всех этих задач необходимо подготовить данные перед тем, как они попадут в ноду `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 — это выбор правильного формата вывода в настройках ноды.
- `Plain text`: нода вернет `msg.payload` в виде строки, даже если она выглядит как JSON.
- `Parsed JSON`: нода попытается распарсить сгенерированную строку как JSON. Если это удастся, `msg.payload` на выходе будет полноценным JavaScript-объектом. Если нет — возникнет ошибка.
Использование `Parsed JSON` — предпочтительная практика, так как это гарантирует синтаксическую корректность вашего JSON перед отправкой.
Кейс: управление яркостью светильника DALI
Представим, что на объекте (например, в офисе) используется освещение по протоколу DALI. Управление осуществляется через шлюз, который принимает команды по MQTT в строго определенном JSON-формате.
Задача: создать поток, который принимает простое значение яркости (например, из слайдера на дашборде) и формирует из него правильный JSON-запрос.
{
"payload": 80, // Яркость в процентах
"topic": "dali/office/group_1"
}
{
"command": "set_brightness_percent",
"target_group": "group_1",
"value": 80,
"fade_time_ms": 1000,
"request_id": "req-1698399512345"
}
* 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:
- Числа vs. Строки: Обратите внимание, что `{{payload}}` вставлено без кавычек, так как `value` должно быть числом. А `{{target_group}}` должно быть в кавычках (если сам шаблон не является JSONata выражением), чтобы на выходе получилась валидная JSON-строка `"target_group": "group_1"`. Ошибка с кавычками — самая частая при генерации JSON.
- Использование `{{{...}}}`: Если вы подставляете свойство, которое само уже является JSON-строкой, используйте тройные скобки `{{{payload}}}`. Это отключает HTML-экранирование и вставляет строку "как есть".
На выходе из ноды `Template` мы получим готовый JavaScript-объект в `msg.payload`, который можно сразу направить в ноду `mqtt out` для отправки команды на DALI-шлюз.
---
Итоги и лучшие практики
Нода `Template` — это незаменимый инструмент в арсенале любого инженера автоматизации, работающего с Node-RED. Она заполняет нишу между простыми манипуляциями в `Change` и сложной логикой в `Function`, предоставляя элегантный способ для решения задач форматирования.
📋 Ключевые понятия:
- Шаблонизация: Процесс создания финального текста или данных путем подстановки значений в заранее определенную структуру (шаблон).
- Mustache: Легковесный "logic-less" язык шаблонов, использующий `{{ }}` для подстановок, `{{#...}}` для секций (итерация и условия) и `{{^...}}` для инвертированных секций.
- Генерация строк: Основная задача `Template` — сборка сложных строк из частей и свойств `msg`.
- Форматирование данных: Приведение данных к человекочитаемому (текст, HTML) или машиночитаемому (JSON, XML) виду.
Выбор правильного инструмента
Для эффективной работы критически важно понимать, когда какой узел использовать.
| Если вам нужно... | Используйте... | Пример |
| :--- | :--- | :--- |
| ...установить, заменить, переместить или удалить свойство. | Нода `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 и научимся выполнять сложные преобразования данных одной строкой кода прямо в настройках нод.