Нода `Switch`: маршрутизация сообщений по условиям
Введение в ноду Switch: логический маршрутизатор потоков
В архитектуре потоков Node-RED нода `Switch` выполняет одну из самых критически важных функций — маршрутизация сообщений (message routing). Если представить поток данных как реку, то узел `Switch` — это система шлюзов и каналов, направляющая воду по разным руслам в зависимости от заданных условий. В программировании аналогами этого узла служат условные операторы `if-then-else` или `switch-case`.
Основная задача ноды `Switch` — принять входящий объект `msg` и направить его на один или несколько из своих выходов, основываясь на наборе правил. В отличие от ноды `Change`, которую мы рассматривали в предыдущем уроке, `Switch` по умолчанию не изменяет сообщение. Она лишь принимает решение о его дальнейшей судьбе.
Давайте рассмотрим интерфейс ноды:
* stopping after first match (остановить после первого совпадения): Это режим по умолчанию. Нода проверяет правила последовательно сверху вниз. Как только одно из условий выполняется, сообщение отправляется в соответствующий выход, и дальнейшая проверка прекращается. Это аналог конструкции `if ... else if ... else`.
* checking all rules (проверять все правила): В этом режиме нода проверяет все правила, и если несколько из них окажутся истинными, копия сообщения будет отправлена в каждый из соответствующих выходов.
В итоге, нода `Switch` является ключевым инструментом для создания нелинейных, ветвящихся потоков и реализации сложной логики принятия решений. Она позволяет системе реагировать на разные события по-разному, фильтровать ненужные данные и направлять команды только тем исполнительным механизмам, которым они предназначены.
---
Базовая конфигурация: проверка значения msg.payload
Наиболее распространенный сценарий использования ноды `Switch` — это маршрутизация на основе значения `msg.payload`. Практически любое устройство или сервис передает свое состояние или данные именно в этом свойстве. Это может быть строковая команда от MQTT-кнопки, числовое значение от датчика температуры или булево значение `true`/`false`.
Процесс настройки прост:
> 💡 Подсказка: Режим "Остановить после первого совпадения" (stopping after first match) работает как `if ... else if ... else`. Сообщение будет отправлено только в первый совпавший выход. Это наиболее частый и производительный сценарий. Режим "Проверять все правила" (checking all rules) позволяет отправить копию сообщения на все выходы, условия для которых истинны, и используется для более сложных сценариев, например, когда одно событие должно запустить несколько параллельных действий.
Таблица основных операторов сравнения
| Оператор | Тип данных | Описание |
| --------------------- | --------------- | ------------------------------------------------------------------------------------------------------- |
| is equal to (`==`) | Строка, Число | Правило истинно, если свойство точно равно указанному значению. |
| is not equal to (`!=`) | Строка, Число | Правило истинно, если свойство не равно указанному значению. |
| is greater than (`>`) | Число | Правило истинно, если значение свойства (число) больше указанного. |
| is less than (`<`) | Число | Правило истинно, если значение свойства (число) меньше указанного. |
| is true / is false| Boolean | Специальные операторы для проверки булевых значений `true` или `false`. |
| contains | Строка | Правило истинно, если указанная подстрока содержится внутри значения свойства. |
| is null | Любой | Правило истинно, если свойство `null` или не существует (`undefined`). |
Практический пример: обработка нажатий беспроводной кнопки
Представим, что у нас есть беспроводная Zigbee-кнопка, которая публикует в MQTT-топик `devices/buttons/living_room/action` сообщения с разным `payload` в зависимости от типа нажатия:
- Одинарное нажатие: `msg.payload` = `"single"`
- Двойное нажатие: `msg.payload` = `"double"`
- Долгое удержание: `msg.payload` = `"hold"`
Наша задача — управлять разными группами света в зависимости от нажатия.
Flow Diagram: +-------------------+ Выход 1 (команда вкл/выкл основной свет)
[mqtt in] ---------> | Switch |---------------------------------------------+
(слушаем кнопку) | (проверка payload) | |
+-------------------+ Выход 2 (команда вкл/выкл торшер) |
|---------------------------------------------+ |
| Выход 3 (команда вкл/выкл подсветку) |
+---------------------------------------------+ |
v v
[change] -> [mqtt out]
Настройка ноды `Switch`:
* `is equal to` (string) `single` -> Выход 1
* `is equal to` (string) `double` -> Выход 2
* `is equal to` (string) `hold` -> Выход 3
{
"topic": "devices/buttons/living_room/action",
"payload": "single",
"_msgid": "..."
}
* Правило 1: `"single"` == `"single"`? Истина.
* Сообщение отправляется в Выход 1.
* Проверка прекращается.
Таким образом, одна нода `Switch` элегантно решает задачу логической маршрутизации, позволяя одному и тому же устройству ввода выполнять совершенно разные функции.
---
Расширенная маршрутизация: другие свойства msg и типы данных
Хотя проверка `msg.payload` является самым частым случаем, возможности ноды `Switch` гораздо шире. Вы можете строить условную логику, основываясь на любом свойстве объекта `msg` или даже на переменных контекста.
> ⚠️ Внимание: При создании сложных систем избегайте проверок, завязанных на глубоко вложенные или сложные структуры в `msg`, если это можно решить на уровне `msg.topic`. Маршрутизация по топику обычно более производительна и делает поток более читаемым.
Маршрутизация по `msg.topic`
Это второй по популярности сценарий, особенно при работе с MQTT. Как мы знаем из предыдущих уроков, иерархия топиков — это мощный инструмент для организации данных. Нода `Switch` позволяет легко фильтровать и разделять потоки сообщений по темам.
Пример: Контроллер получает данные от комбинированного датчика, который публикует температуру и влажность в разные топики:- `sensors/living_room/temperature`
- `sensors/living_room/humidity`
Нам нужно направить эти данные в разные логические ветки для обработки (например, для управления отоплением и увлажнителем).
Настройка ноды `Switch`:* `contains` (string) `temperature` -> Выход 1 (ветка обработки температуры)
* `contains` (string) `humidity` -> Выход 2 (ветка обработки влажности)
Теперь все сообщения, в топике которых есть слово `temperature`, пойдут на первый выход, а все с `humidity` — на второй. Это гораздо эффективнее, чем использовать две отдельные ноды `mqtt in` для каждого топика.
Проверка типа данных (`is type`)
Иногда важно не само значение, а его тип. Например, вы ожидаете от датчика число, а получаете строку из-за ошибки в прошивке устройства. Нода `Switch` может выступать в роли валидатора данных.
Пример: Поток должен обрабатывать только числовые значения. Настройка ноды `Switch`:* `is of type` `Number` -> Выход 1 (корректные данные, отправляем на обработку)
* `otherwise` -> Выход 2 (некорректные данные, логируем ошибку)
Это простой, но очень эффективный способ повысить надежность вашей системы, отсеивая мусорные данные на самом входе.
Использование `otherwise` (иначе)
Выход `otherwise` — это эквивалент блока `else` в программировании. Он срабатывает, если ни одно из вышестоящих правил не оказалось истинным. Это крайне полезно для:
- Обработки ошибок: Логировать или отправлять уведомления о непредусмотренных значениях.
- Действий по умолчанию: Если сообщение не соответствует ни одному из специальных случаев, выполнить стандартное действие.
Использование `otherwise` делает ваш поток более полным и предсказуемым, так как вы явно обрабатываете все возможные исходы.
---
Продвинутые условия с помощью JSONata
Когда стандартных операторов сравнения (`==`, `>`, `<` и т.д.) становится недостаточно, на помощь приходит JSONata. Это мощный язык запросов и преобразований для JSON-данных, который встроен во многие ноды Node-RED, включая `Switch`.
Чтобы использовать JSONata в ноде `Switch`, нужно в выпадающем списке типа правила выбрать Expression (`J:`). Появится поле для ввода выражения, которое должно вернуть `true` (если условие выполнено) или `false` (если не выполнено).
JSONata незаменима в следующих случаях:
- Нужно проверить значение во вложенном объекте.
- Нужно проверить несколько условий одновременно в одном правиле.
- Нужно выполнить вычисления или преобразования перед сравнением.
Пример 1: Проверка вложенного свойства
Предположим, датчик присылает данные в сложном формате JSON:
{
"payload": {
"device_id": "termo-kitchen-01",
"data": {
"temperature": 25.4,
"battery": 98
},
"timestamp": 1678886400000
}
}
Нам нужно включить кондиционер, если температура (`payload.data.temperature`) превышает 25 градусов. Стандартными средствами это сделать невозможно.
JSONata-выражение:payload.data.temperature > 25
Это выражение будет применено к объекту `msg` и вернет `true`, если температура действительно выше 25, направив сообщение в соответствующий выход.
Пример 2: Комбинированная проверка
А теперь усложним задачу. Свет в гостиной должен включаться, только если:
$contains(topic, "living_room") and payload = "ON"
Здесь мы используем:
- `$contains(str, substr)`: встроенная функция JSONata для проверки наличия подстроки.
- `and`: логический оператор "И".
Это позволяет создать очень специфичные и точные правила маршрутизации в одной ноде, делая поток компактным и читаемым.
Пример 3: Проверка длины массива
Иногда в `msg.payload` приходит массив данных (например, список активных устройств). Нам нужно выполнить действие, только если в списке есть хотя бы одно устройство.
JSONata-выражение:$count(payload) > 0
- `$count()`: функция для подсчета элементов в массиве.
Это выражение вернет `true`, если массив в `msg.payload` не пустой.
---
Практический кейс: управление светом по датчику движения и времени суток
Давайте соберем все полученные знания в одном комплексном сценарии, который демонстрирует мощь ноды `Switch` как центрального элемента принятия решений.
Сценарий: Свет в коридоре должен включаться автоматически при обнаружении движения, но только в ночное время. Днем свет включаться не должен, чтобы экономить электроэнергию.Для реализации нам понадобится переменная, хранящая текущее состояние "день/ночь". Будем использовать переменную контекста потока `flow.isNight`.
Шаг 1: Установка переменной контекстаСамый простой способ — использовать две ноды `Inject` для ручного переключения. В реальной системе это будет делать узел, отслеживающий время восхода/заката (например, `node-red-contrib-sun-position`).
- Inject 1 (Ночь): Отправляет `true`, нода `Change` устанавливает `flow.isNight` в `true`.
- Inject 2 (День): Отправляет `false`, нода `Change` устанавливает `flow.isNight` в `false`.
Датчик движения при срабатывании отправляет в MQTT сообщение с `payload`, равным `{"motion": true}`.
Шаг 3: Построение логики с помощью `Switch`Когда от датчика приходит сообщение, нам нужно проверить два условия:
Для этого мы можем использовать две последовательные ноды `Switch`:
[mqtt in] (msg от датчика)
|
v
+-----------------------------+
| Switch 1: Проверка движения | (Правило: payload.motion is true)
+-----------------------------+
| (если ДА)
v
+----------------------------+
| Switch 2: Проверка ночи | (Правило: flow.isNight is true)
+----------------------------+
| (если ДА)
v
[change] (формируем команду "ON") --> [mqtt out] (отправляем на реле света)
Настройка `Switch 1`:
- Property: `msg.payload.motion` (используем JSON-путь)
- Правило: `is true` -> Выход 1
- Все сообщения без `{ "motion": true }` будут отфильтрованы.
- Property: `flow.isNight` (выбираем `flow` в выпадающем списке)
- Правило: `is true` -> Выход 1
Такая последовательная фильтрация очень наглядна и легко отлаживается. Сообщение дойдет до реле света, только если пройдет через "ворота" обоих `Switch`-узлов.
Этот пример наглядно демонстрирует, как с помощью ноды `Switch` строятся нетривиальные сценарии автоматизации, которые учитывают множество факторов (событие от датчика, внешнее состояние) для принятия верного решения.
Что дальше
В этом уроке мы досконально изучили ноду `Switch` — от базовой маршрутизации по `payload` до сложных условий с JSONata и контекстом. Теперь вы можете создавать потоки с ветвлениями и сложной логикой. В следующем уроке мы рассмотрим ноду `Function`, которая открывает безграничные возможности для обработки данных и реализации любой, даже самой сложной логики с помощью JavaScript.