Реализация блокировки с помощью нод Switch и Gate
Введение в логические блокировки в Node-RED
Взаимная блокировка, или интерлок (interlock), является краеугольным камнем в проектировании безопасных систем автоматизации. Ее основная задача — предотвратить одновременное выполнение взаимоисключающих действий, которые могут привести к повреждению оборудования, созданию опасной ситуации или просто к некорректной работе системы. Классический пример — управление реверсивным двигателем, где одновременная подача команд «Вверх» и «Вниз» вызывает короткое замыкание и выход из строя как самого двигателя, так и управляющего реле.
> 🔗 Связанный материал: Для полного понимания концепции, пожалуйста, ознакомьтесь с предыдущим уроком «Концепция взаимной блокировки (Interlock)». Мы будем опираться на изложенные в нем принципы.
Хотя многие исполнительные устройства, такие как приводы штор или ворот, имеют встроенные аппаратные защиты, полагаться только на них — рискованная стратегия. Аппаратная защита может отказать, а контроллер, не имеющий собственной логики блокировки, продолжит посылать некорректные команды, усугубляя ситуацию. Программная блокировка на уровне контроллера является первым и самым важным рубежом обороны. Она позволяет предотвратить саму возможность отправки конфликтующей команды, обеспечивая предсказуемость и надежность системы.
В среде Node-RED для реализации логических блокировок существует два основных инструмента, которые мы сегодня подробно разберем:
В рамках этого урока нашей главной задачей будет создание надежной программной блокировки для управления реверсивным двигателем, используя стандартные релейные выходы контроллера HI и логические возможности Node-RED.
---
Нода Switch: Маршрутизация на основе состояния
Нода `Switch` — один из самых мощных и часто используемых стандартных узлов в Node-RED. Ее основная функция заключается в проверке входящего сообщения (`msg`) по одному или нескольким правилам и его маршрутизации на разные выходы в зависимости от результатов проверки. В контексте взаимных блокировок мы будем использовать ее способность проверять не только `msg.payload`, но и переменные контекста, которые, как мы уже знаем, хранят текущее состояние нашей системы.
> 💡 Подсказка: Для наглядной отладки потоков с использованием `Switch` и контекстных переменных, активно используйте ноду `Debug`. Настройте ее на вывод «complete msg object». Это позволит вам видеть не только `payload`, но и то, как изменяются переменные контекста (`flow` или `global`) при прохождении сообщения через разные ветви логики.
Принцип работы и конфигурация
При настройке ноды `Switch` ключевым является поле «Property» (Свойство). По умолчанию там установлено `msg.payload`, но мы можем выбрать опцию «flow» (переменная потока) или «global» (глобальная переменная) и указать имя переменной, которую хотим проверить.
Представим, что мы управляем рулонной шторой и храним ее текущее состояние в переменной `flow.shutter_state`. Эта переменная может принимать значения: `stopped`, `moving_up`, `moving_down`.
Наша задача: заблокировать команду «Вниз», если штора уже движется («Вверх» или «Вниз»).
Конфигурация ноды `Switch`, стоящей на пути команды «Вниз», будет выглядеть так:
* Правило 1: `==` (строка) `stopped` -> Выход 1
* Правило 'otherwise' (иначе): -> Выход 2
Логика работы потока:- Когда приходит команда «Вниз», она попадает на вход ноды `Switch`.
- Нода `Switch` не смотрит на само сообщение, а немедленно проверяет значение переменной `flow.shutter_state`.
- Если `flow.shutter_state` равно `stopped`, правило 1 срабатывает, и сообщение с командой «Вниз» проходит через Выход 1 к узлу, который активирует соответствующее реле.
- Если `flow.shutter_state` имеет любое другое значение (`moving_up` или `moving_down`), срабатывает правило `otherwise`, и сообщение уходит на Выход 2. Этот выход мы можем подключить к ноде `Debug` для логирования попытки некорректного действия или просто оставить неподключенным, эффективно блокируя (отбрасывая) команду.
Такой подход позволяет создать очень гибкую и читаемую логику, где маршрутизация команды явно зависит от текущего состояния системы.
Практический пример
Допустим, на вход потока приходит команда в виде JSON:
{
"topic": "commands/shutter/living_room",
"payload": {
"action": "DOWN",
"source": "wall_switch_3"
}
}
Этот поток разделяется на две ветки: одна для `DOWN` и одна для `UP`. Рассмотрим ветку `DOWN`:
[MQTT In] --(msg)--> [Switch: filter DOWN] --(msg)--> [Switch: Check State] --+-- (to Relay)
|
+-- (to Log)
Нода `[Switch: Check State]` настроена, как описано выше:
- Свойство: `flow.shutter_state`
- Правило 1: `== "stopped"`
- Правило 2: `otherwise`
Когда штора приводится в движение, другая часть потока (например, нода `Function` после `Switch`) должна немедленно обновить состояние:
`flow.set("shutter_state", "moving_down");`
Теперь, если в этот момент придет еще одна команда `DOWN` или команда `UP`, соответствующие ноды `Switch` в их потоках корректно заблокируют эти команды, так как состояние системы больше не `stopped`.
---
Нода Gate: Простое открытие и закрытие потока
Если нода `Switch` — это интеллектуальный диспетчер с множеством правил, то нода `Gate` (из популярной библиотеки `node-red-contrib-simple-gate`) — это простой, но эффективный шлагбаум. Она имеет всего два состояния: `open` (открыто) и `closed` (закрыто).
- В состоянии `open` нода `Gate` пропускает все проходящие через нее сообщения без изменений.
- В состоянии `closed` нода `Gate` блокирует сообщения. Опционально, она может помещать последнее заблокированное сообщение в очередь и отправлять его, как только `Gate` снова откроется.
Основное преимущество `Gate` в ее явном управлении. Состояние ноды меняется не косвенно (через проверку контекста), а напрямую, с помощью специальных управляющих сообщений.
Управление состоянием ноды Gate
Чтобы изменить состояние `Gate`, необходимо отправить на ее вход сообщение, у которого `msg.topic` имеет одно из следующих строковых значений:
| `msg.topic` | Действие |
| :---------- | :-------------------------------------------------------------------- |
| `open` | Открывает "ворота". Если в очереди было сообщение, оно отправляется. |
| `close` | Закрывает "ворота". Все последующие сообщения будут заблокированы. |
| `toggle` | Переключает состояние: если было открыто — закрывает, и наоборот. |
Это позволяет строить очень понятную логику блокировки: при запуске одного действия мы явно отправляем команду на закрытие «ворот» для конфликтующего действия.
Пример реализации с Gate
Вернемся к задаче управления шторой. Построим логику с использованием двух нод `Gate`:
- `Gate-UP`: защищает поток, ведущий к реле «Вверх».
- `Gate-DOWN`: защищает поток, ведущий к реле «Вниз».
Поток для команды «Вверх» будет выглядеть так:
+--(команда "Вверх")------------------> [Gate-UP] ---> [Реле "Вверх"]
|
[Источник] --+--(команда "Стоп")
|
+--(команда "Вниз")-------------------> [Gate-DOWN] --> [Реле "Вниз"]
Теперь добавим управляющую логику.
* Основное сообщение проходит через (изначально открытый) узел `Gate-UP` к реле.
* Параллельно мы формируем и отправляем управляющее сообщение `{"topic": "close"}` на вход узла `Gate-DOWN`. `Gate-DOWN` немедленно закрывается.
* Любая последующая команда «Вниз» будет заблокирована узлом `Gate-DOWN`.
* Логика зеркальна: основное сообщение идет к реле «Вниз», а управляющее сообщение `{"topic": "close"}` отправляется на `Gate-UP`.
* Команда «Стоп» отправляет управляющие сообщения `{"topic": "open"}` на оба узла: и на `Gate-UP`, и на `Gate-DOWN`.
* Система возвращается в исходное состояние, оба направления снова доступны для команд.
Пример управляющего сообщения, которое закрывает `Gate-DOWN`:
{
"topic": "close",
"payload": "Блокируем направление 'Вниз', так как начато движение 'Вверх'"
}
Нода `Gate` проигнорирует `payload`, но отреагирует на `topic`. Использование осмысленного `payload` полезно для отладки.
---
Практический пример: Блокировка двигателя рулонной шторы
Теперь соберем все воедино и построим полноценный, безопасный поток для управления мотором рулонной шторы на базе контроллера HI. Мы будем использовать два релейных выхода: `RL-01` для движения вверх и `RL-02` для движения вниз.
> ⚠️ Внимание: Критически важно исключить любую возможность одновременного включения реле «Вверх» и «Вниз». У большинства реверсивных приводов это приводит к короткому замыканию на стороне блока питания или самого двигателя, что может привести к физическому выходу из строя дорогостоящего оборудования. Всегда отлаживайте логику на контроллере без подключения реальной силовой нагрузки, наблюдая за срабатыванием реле по светодиодным индикаторам на контроллере.
Сборка комплексного потока
Задача: Обрабатывать MQTT-команды из топика `hi/office/shutter1/set` с `payload` "UP", "DOWN", "STOP". Реализовать взаимную блокировку и обеспечить автоматическую остановку через 15 секунд. ASCII-схема потока: +--> [Function: Set state UP] ----+
| |
[MQTT In] -> [Switch: "UP"/"DOWN"/"STOP"] --(UP)------>| +--> [Gate-UP] -> [RL-01 ON] -> [Trigger 15s] -> [RL-01 OFF] -> [Func: Set stopped]
| |
| | (DOWN)--> [Function: Set state DOWN] --+
| | | |
| | +--> [Gate-DOWN] -> [RL-02 ON] -> [Trigger 15s] -> [RL-02 OFF] -> [Func: Set stopped]
| |
+-----(STOP)-+-------------------------------> [Function: STOP Logic] -----> [RL-01/02 OFF] & [Func: Set stopped]
Шаг 1: Инициализация состояния
Используя шаблон 'Inject-Once', создаем начальное состояние при старте Node-RED.
`[Inject: once] -> [Function: Init State]`
Код в `[Function: Init State]`:
// Устанавливаем начальное состояние - штора остановлена
flow.set("shutter_state", "stopped");
// Убеждаемся, что оба "гейта" открыты
node.send([
{ topic: "open", payload: "init" }, // Сообщение для Gate-UP
{ topic: "open", payload: "init" } // Сообщение для Gate-DOWN
]);
return null; // Больше ничего не отправляем
Эта нода имеет два выхода, подключенных к `Gate-UP` и `Gate-DOWN`.
Шаг 2: Маршрутизация командНода `[Switch: "UP"/"DOWN"/"STOP"]` проверяет `msg.payload` и направляет сообщение на один из трех выходов.
Шаг 3: Логика для команд "UP" и "DOWN" (на примере "UP")- `[Function: Set state UP]`: Эта нода — ключ к блокировке.
// Получаем текущее состояние
const state = flow.get("shutter_state") || "stopped";
// ПРОВЕРКА БЛОКИРОВКИ
if (state !== "stopped") {
node.warn(`Блокирована команда UP. Текущее состояние: ${state}`);
return null; // Блокируем сообщение
}
// Если блокировки нет, МЕНЯЕМ СОСТОЯНИЕ
flow.set("shutter_state", "moving_up");
node.status({fill:"blue", shape:"dot", text:`Движение вверх...`});
// Формируем команды
// 1. Управляющая команда для закрытия гейта "Вниз"
const closeGateCmd = { topic: "close" };
// 2. Исполнительная команда для реле "Вверх"
const driveUpCmd = { payload: true }; // Команда на включение реле
// Отправляем команды на разные выходы ноды
return [ closeGateCmd, driveUpCmd ];
Эта нода имеет два выхода: первый подключен к `Gate-DOWN`, второй — к `Gate-UP`.
- `[Gate-UP]` и `[Gate-DOWN]`: Защищают потоки к реле.
- `[RL-01 ON]`: Нода управления физическим реле.
- `[Trigger 15s]`: Как мы рассматривали в уроке про таймауты, эта нода ждет 15 секунд, а затем отправляет сообщение для выключения реле.
- `[RL-01 OFF]`: Выключает реле.
- `[Func: Set stopped]`: После выключения реле эта нода обновляет состояние системы.
flow.set("shutter_state", "stopped");
node.status({fill:"green", shape:"dot", text:"Остановлено"});
// Также открываем оба гейта для следующей команды
return [ {topic: "open"}, {topic: "open"} ];
Эта нода также имеет два выхода для отправки команды `open` на оба `Gate`.
Шаг 4: Логика для команды "STOP"`[Function: STOP Logic]` — это команда с наивысшим приоритетом.
// Мгновенно обновляем состояние
flow.set("shutter_state", "stopped");
node.status({fill:"green", shape:"dot", text:"Остановлено (команда СТОП)"});
// Формируем массив сообщений для выполнения всех действий
const messages = [
{ payload: false }, // Команда на выключение RL-01
{ payload: false }, // Команда на выключение RL-02
{ topic: "open" }, // Открыть Gate-UP
{ topic: "open" } // Открыть Gate-DOWN
];
return [ messages ]; // Отправляем массив сообщений
Эта нода имеет один выход, который подключен ко всем четырем целям: `RL-01`, `RL-02`, `Gate-UP`, `Gate-DOWN`.
Этот комплексный поток, комбинирующий проверку состояния, явное управление `Gate` и обработку таймаутов, обеспечивает надежную и безопасную работу исполнительного механизма.
---
Заключение: Выбор инструмента и лучшие практики
Мы рассмотрели два мощных инструмента для создания программных взаимных блокировок в Node-RED: `Switch` и `Gate`. Хотя обе ноды могут решать схожие задачи, у них есть свои сильные стороны.
| Критерий | Нода `Switch` | Нода `Gate` |
| :---------------- | :------------------------------------------------------- | :------------------------------------------------------------- |
| Сложность логики | Идеальна для сложных, многовариантных условий и маршрутизации. | Лучше всего подходит для простой двоичной логики "пропустить/заблокировать". |
| Основной принцип | Маршрутизация на основе проверки состояния (косвенное управление). | Прямое управление потоком через команды "open/close" (явное управление). |
| Гибкость | Очень высокая. Может проверять любые свойства `msg` или контекста. | Ограничена двумя состояниями. |
| Визуальная ясность | Логика может быть скрыта внутри настроек. Требует комментариев. | Состояние "шлагбаума" наглядно, управляющие потоки видны. |
Ключевые выводы и лучшие практики:Освоив эти два узла, вы сможете реализовать подавляющее большинство сценариев взаимных блокировок, необходимых для создания безопасных и профессиональных систем автоматизации на платформе HI.
Что дальше?
В следующем уроке мы углубимся в реализацию более сложных конечных автоматов (Finite State Machines) для управления системами с множеством состояний, таких как климат-контроль, где взаимные блокировки между режимами «Нагрев», «Охлаждение» и «Вентиляция» являются критически важными.