`msg.topic`: ключ к маршрутизации и фильтрации
Введение в msg.topic: Больше, чем просто свойство
В предыдущих уроках мы установили, что объект `msg` является фундаментальной единицей передачи информации в Node-RED, а `msg.payload` — его основным контейнером данных. Теперь мы углубимся в другое критически важное свойство — `msg.topic`. Если `payload` — это что передается, то `topic` — это о чём эти данные, откуда они пришли или куда они направляются.
Для инженера автоматизации `msg.topic` — это прежде всего мощный инструмент для организации и управления потоками данных. Его можно рассматривать как семантический адрес или идентификатор сообщения, который путешествует вместе с данными, но не является их частью.
Самая простая и наглядная аналогия — это электронное письмо:
- `msg.payload` — это тело письма, его основное содержимое.
- `msg.topic` — это строка "Тема" (Subject).
Вы можете получить десять писем с одинаковым содержимым (например, "Встреча перенесена"), но именно тема ("Проект Альфа", "Отчет за Q3", "Срочно: Техническое обслуживание") позволяет вам мгновенно понять контекст и принять решение, что делать с этим письмом, не вчитываясь в него. Точно так же `msg.topic` позволяет узлам в Node-RED эффективно маршрутизировать и фильтровать сообщения, не анализируя сложную структуру `msg.payload`.
> 📋 Ключевые понятия:
> - `msg.topic`: Строковое свойство объекта `msg`, содержащее метаданные о сообщении: его источник, категорию или назначение.
> - Маршрутизация (Routing): Процесс направления сообщения по разным веткам потока в зависимости от его свойств.
> - Фильтрация (Filtering): Процесс отсеивания или пропуска сообщений, соответствующих определенным критериям.
В рамках этого урока мы увидим, как `msg.topic` становится главным инструментом в следующих узлах:
- `switch`: для построения логических ветвлений потока.
- `change`: для динамического присвоения или изменения топика.
- `mqtt in` / `mqtt out`: для интеграции с внешними системами по протоколу MQTT, где концепция топиков является основополагающей.
Правильное использование `msg.topic` отделяет логику управления потоком от логики обработки данных, что делает ваши сценарии более чистыми, масштабируемыми и легкими для отладки — ключевые качества для профессионального инженера-интегратора.
---
Практика: Базовая маршрутизация с помощью узла switch
Узел `switch` — это основной инструмент для реализации условной логики в Node-RED. Хотя он может проверять любое свойство объекта `msg`, его наиболее эффективное и частое применение — это маршрутизация на основе `msg.topic`.
Представим типовую задачу: на контроллер HI поступают данные от двух разных датчиков, подключенных через единый шлюз (например, LoRaWAN или универсальный преобразователь). Шлюз отправляет все сообщения на один и тот же вход в Node-RED, но помечает их разными топиками.
- Датчик температуры: `topic` = `"sensors/room/temperature"`, `payload` содержит температуру.
- Датчик влажности: `topic` = `"sensors/room/humidity"`, `payload` содержит влажность.
Наша задача — направить каждое сообщение на свою собственную ветку обработки.
> 💡 Подсказка: Используйте `topic` для маршрутизации, когда структура `payload` одинакова, а источник или тип данных разный. Это делает flow более читаемым и масштабируемым по сравнению с проверкой внутренних полей `msg.payload`.
Построение потока маршрутизации
* Inject 1 (Температура):
* `msg.payload`: `{"value": 23.5, "unit": "°C", "ts": 1678886400000}` (JSON)
* `msg.topic`: `"sensors/room/temperature"` (String)
* Inject 2 (Влажность):
* `msg.payload`: `{"value": 45, "unit": "%", "ts": 1678886500000}` (JSON)
* `msg.topic`: `"sensors/room/humidity"` (String)
* Property: Убедитесь, что в верхнем поле выбрано `msg.topic`.
* Правила маршрутизации: Добавьте два правила:
1. `==` (равно) `sensors/room/temperature`
2. `==` (равно) `sensors/room/humidity`
Узел `switch` теперь будет иметь два выхода. Первый сработает, если `msg.topic` точно совпадет со строкой `"sensors/room/temperature"`, второй — для влажности.
Inject 1 (Temp) --+
|
+--> [switch on msg.topic] --+-- (выход 1) --> [debug: "Обработка температуры"]
| |
Inject 2 (Hum) ---+ +-- (выход 2) --> [debug: "Обработка влажности"]
При нажатии на первый `inject`, сообщение появится только у первого `debug`. При нажатии на второй — только у второго. Мы успешно разделили поток данных, не заглядывая в `msg.payload`.
Сравнение с маршрутизацией по `msg.payload`
А что, если бы мы маршрутизировали по `msg.payload.unit`?
- Правило 1: `msg.payload.unit` == `"°C"`
- Правило 2: `msg.payload.unit` == `"%"`
Это тоже будет работать. Однако такой подход менее надежен и масштабируем:
- Хрупкость: Если изменится формат `payload` (например, `unit` переименуют в `units`), вся маршрутизация сломается. `topic` более стабилен.
- Производительность: Проверка строкового `msg.topic` немного быстрее, чем разбор JSON-объекта и доступ к его вложенному свойству. На больших объемах сообщений это может иметь значение.
- Читаемость: Глядя на узел `switch`, настроенный на `msg.topic`, другой инженер сразу понимает его назначение — это маршрутизатор по типу данных. `switch`, настроенный на глубоко вложенное свойство `payload`, требует дополнительного анализа.
Использование `msg.topic` для маршрутизации является одним из ключевых паттернов проектирования надежных потоков в Node-RED.
---
Динамическое управление topic в узле change
Если `switch` читает `msg.topic` для принятия решений, то узел `change` позволяет его модифицировать или создавать "на лету". Это необходимо, когда сообщения приходят из источников, которые не задают `topic`, или когда нам нужно подготовить сообщение для отправки в систему, требующую строгой структуры топиков, например, MQTT.
Узел `change` предоставляет несколько мощных правил для работы с `msg.topic`.
1. Установка статического топика
Самый простой случай: у нас есть сообщение без `topic`, и ему нужно присвоить фиксированное значение. Например, сигнал от физической кнопки, подключенной к универсальному входу `UI-05` контроллера HI, должен получить топик `"events/button/office_main_light"`.
* Создайте правило: Set `msg.topic` to `events/button/office_main_light` (выберите тип `string`).
Теперь любое сообщение, проходящее через этот узел `change`, получит указанный `topic`, сохраняя при этом свой оригинальный `payload`.
Пример `msg` до и после узла `change`:// ДО узла 'change'
{
"payload": true,
"_msgid": "a1b2c3d4.e5f6g7"
}
// ПОСЛЕ узла 'change'
{
"payload": true,
"topic": "events/button/office_main_light",
"_msgid": "a1b2c3d4.e5f6g7"
}
2. Формирование иерархических topic
Намного более интересный сценарий — это создание `topic` на основе другой информации в сообщении. Это позволяет создавать гибкие и структурированные пространства имен, что особенно важно для MQTT.
Предположим, узел-драйвер Modbus считывает данные с климатического модуля и формирует `msg` со следующей структурой:
{
"payload": {
"value": 24.1
},
"device_info": {
"location": "room101",
"floor": "floor2",
"type": "temperature"
}
}
Мы хотим отправить эти данные в MQTT с топиком, имеющим вид `hi_platform/floor2/room101/temperature/value`. Это можно сделать с помощью правила `Set` в узле `change` и языка выражений JSONata.
Настройка узла `change` для динамического топика:- Правило: Set `msg.topic` to ... (выберите тип `expression` - `J:`).
- В поле выражения введите:
"hi_platform/" & device_info.floor & "/" & device_info.location & "/" & device_info.type & "/value"
Это выражение конкатенирует (склеивает) статические строки (`"hi_platform/"`) со значениями из объекта `msg.device_info`. Результатом будет сгенерированный `topic`, идеально подходящий для структурированной MQTT-иерархии.
> ℹ️ Информация: JSONata — это мощный язык запросов и преобразований для JSON, встроенный в Node-RED. Он позволяет выполнять сложные манипуляции с данными прямо в узлах `change` и `switch` без написания кода в узле `function`.
Этот подход позволяет создать единый поток обработки для множества однотипных устройств. Достаточно, чтобы их драйверы предоставляли информацию в стандартизированном виде, а узел `change` сам построит правильные топики для каждого из них.
---
msg.topic и протокол MQTT: Основа взаимодействия
Взаимосвязь `msg.topic` и протокола MQTT настолько тесна, что можно сказать — они созданы друг для друга. В MQTT топик — это не просто свойство, а фундаментальная концепция, адрес, по которому сообщения публикуются (`publish`) и на который клиенты подписываются (`subscribe`). Node-RED элегантно интегрирует эту концепцию через `msg.topic`.
Узел `mqtt in`: От MQTT к Node-RED
Когда вы настраиваете узел `mqtt in` для подписки на топик (например, `devices/+/status`), он делает две вещи при получении сообщения:
Это позволяет не только получать данные, но и сразу знать их источник. Если `mqtt in` подписан на `devices/+/status`, то при получении сообщения по топику `devices/thermostat-01/status`, `msg.topic` в Node-RED будет равен именно `devices/thermostat-01/status`. Это дает нам готовый ключ для маршрутизации с помощью узла `switch`, как мы рассматривали ранее.
Узел `mqtt out`: От Node-RED к MQTT
Узел `mqtt out` работает в обратном направлении. При публикации сообщения он ищет топик в двух местах, в следующем порядке приоритета:
> ⚠️ Внимание: Если `msg.topic` не задан и поле "Topic" в узле `mqtt-out` пусто, сообщение НЕ будет отправлено. Узел `mqtt-out` просто проигнорирует его, не выдав видимой ошибки (если не использовать узел `Status`). Это частая ошибка начинающих инженеров, приводящая к часам отладки.
Поэтому перед каждым узлом `mqtt out` (если его собственное поле "Topic" не заполнено) должна стоять логика, гарантирующая наличие `msg.topic`, например, с помощью узла `change`.
Стратегия именования топиков
Для надежной и масштабируемой системы автоматизации (офис, гостиница, большой дом) необходима согласованная стратегия именования топиков.
| Хорошие топики | Плохие топики |
| -------------------------------------------------- | ------------- |
| `hi/building-A/floor-3/room-305/light/main/set` | `light_on` |
| `hi/building-A/floor-3/room-305/temp/value` | `temp_sensor` |
| `hi/system/controller-01/status/online` | `status` |
Принципы хорошей структуры топиков:- Иерархичность: Уровни, разделенные слэшем (`/`), отражают физическую или логическую структуру объекта (Проект -> Здание -> Этаж -> Помещение -> Устройство -> Параметр).
- Специфичность: Топик однозначно идентифицирует источник или назначение данных.
- Предсказуемость: Легко угадать топик для любого устройства в системе.
- Единообразие: Используются команды `set` для отправки команд и `status` или `value` для получения состояний.
Такая структура позволяет использовать мощные механизмы фильтрации, которые мы рассмотрим далее.
---
Пример: Продвинутая фильтрация с Wildcards (+ и #)
Одно из главных преимуществ иерархической структуры топиков MQTT — возможность использовать специальные символы подстановки (wildcards) при подписке. Это позволяет одному узлу `mqtt in` получать сообщения из целой группы топиков.
> 📋 Ключевые понятия:
> - `+` (Плюс, одноуровневый wildcard): Заменяет ровно один уровень в иерархии топиков. Например, `hi/floor-1/+/light/status` подпишется на `.../room-101/light/status` и `.../corridor/light/status`, но не на `.../room-101/main-light/status` или `.../light/status`.
> - `#` (Решетка, многоуровневый wildcard): Заменяет ноль или более уровней в иерархии. Должен быть последним символом в топике. Например, `hi/floor-1/#` подпишется на абсолютно все сообщения, относящиеся к первому этажу.
Практический пример: единый обработчик статуса освещения
Задача: Создать поток, который логирует в базе данных MySQL любое изменение состояния светильников на всем первом этаже.Вместо того чтобы создавать десятки узлов `mqtt in` для каждого светильника (`hi/floor-1/room-101/light/status`, `hi/floor-1/room-102/light/status` и т.д.), мы можем использовать один узел с wildcard.
* Broker: Выберите ваш MQTT-брокер.
* Topic: `hi/floor-1/+/light/status`.
* QoS: `1`.
Теперь этот узел будет получать все сообщения об изменении статуса света на первом этаже. Важно, что `msg.topic` в Node-RED будет содержать полный топик, по которому пришло сообщение. Это позволяет нам в дальнейшем потоке определить, какой именно светильник изменил состояние.
Схема потока (ASCII):[mqtt in: hi/floor-1/+/light/status] -> [function: "Подготовить данные для SQL"] -> [mysql: "Записать в лог"]
Код для узла `function`:
// Входящий msg.topic может быть "hi/floor-1/room-101/light/status"
// Входящий msg.payload может быть "ON" или "OFF"
// Извлекаем название помещения из топика
const topicParts = msg.topic.split('/'); // ["hi", "floor-1", "room-101", "light", "status"]
const room = topicParts[2]; // "room-101"
const state = msg.payload;
// Формируем SQL-запрос
msg.query = `INSERT INTO light_log (room, state, timestamp) VALUES (?, ?, NOW())`;
msg.payload = [room, state]; // Параметры для защиты от SQL-инъекций
return msg;
Этот пример демонстрирует всю мощь связки `msg.topic` и wildcards: один компактный поток может обрабатывать события от сотен устройств, оставаясь при этом читаемым и эффективным.
Имитация Wildcards внутри Node-RED с помощью Regex
Иногда нужно выполнить подобную сложную фильтрацию не в `mqtt in`, а прямо внутри потока с помощью узла `switch`. Стандартные правила `==`, `contains` тут не помогут. На помощь приходят регулярные выражения (regex).
Предположим, все сообщения уже в Node-RED, и мы хотим отфильтровать те же `hi/floor-1/+/light/status`.
В узле `switch` можно настроить правило:
- Property: `msg.topic`
- Rule: `matches regex`
- Value: `^hi/floor-1/[^/]+/light/status$`
Это регулярное выражение делает то же самое, что и MQTT-wildcard `+`.
---
Итоги и лучшие практики
В этом уроке мы глубоко погрузились в `msg.topic` и убедились, что это не просто вспомогательное свойство, а один из краеугольных камней проектирования эффективных и надежных потоков в Node-RED.
Ключевые выводы:- Разделяйте данные и метаданные. Запомните главный принцип: `msg.payload` — это данные, `msg.topic` — это метаданные. `topic` описывает контекст, источник или назначение данных, не смешиваясь с ними.
- Используйте `switch` для маршрутизации. Применяйте узел `switch`, настроенный на `msg.topic`, для создания чистых и понятных логических ветвлений в ваших потоках. Это предпочтительнее, чем сложная логика, завязанная на содержимое `payload`.
- Используйте `change` для модификации. Узел `change` — ваш основной инструмент для присвоения или динамического формирования топиков, особенно при подготовке сообщений для отправки в MQTT.
- Проектируйте структуру топиков. При работе с MQTT всегда разрабатывайте и придерживайтесь иерархической, осмысленной стратегии именования топиков. Это фундамент для масштабируемой и управляемой системы.
- Проверяйте наличие `topic`. Перед узлами, которые неявно полагаются на `msg.topic` (в первую очередь `mqtt out`), всегда убеждайтесь, что это свойство установлено.
Освоив `msg.topic`, вы переходите от простого соединения узлов к осознанному проектированию архитектуры потоков данных. Это навык, который отличает новичка от профессионального инженера автоматизации.
Что дальше
В следующем уроке мы расширим наши знания об управлении состоянием и контекстом, рассмотрев узел `trigger`. Он позволяет создавать сложные временные последовательности и управлять потоками, которые должны реагировать не только на приходящие сообщения, но и на отсутствие таковых. Мы научимся строить сторожевые таймеры (watchdogs), детекторы "зависания" датчиков и реализовывать логику, требующую выдержки времени.