Flow Diagram: Сценарий управления реверсивным двигателем
Введение: Постановка задачи для реверсивного двигателя
В современных системах автоматизации мы постоянно сталкиваемся с необходимостью управлять устройствами, которые имеют два противоположных направления движения. Классическими примерами являются моторизованные шторы или жалюзи, гаражные или въездные ворота, рольставни или даже клапаны с реверсивным приводом в системах климат-контроля. Сердцем таких механизмов является реверсивный двигатель.
Такой двигатель, как правило, имеет две отдельные обмотки (или два входа для управляющей электроники): одну для прямого хода (например, «Поднять шторы») и одну для обратного ( «Опустить шторы»). Управление ими осуществляется с помощью двух независимых релейных выходов контроллера HI. Подавая напряжение на первое реле, мы запускаем движение в одну сторону; подавая на второе — в другую.
Здесь и возникает критический физический риск. Что произойдет, если по ошибке программного обеспечения или из-за некорректной логики напряжение будет подано одновременно на обе обмотки? Это приведет к короткому замыканию внутри двигателя. Последствия могут быть катастрофическими:
- Мгновенный выход двигателя из строя: Перегорание обмоток.
- Повреждение блока питания или релейного модуля: Из-за сверхтоков, возникающих при коротком замыкании.
- Риск возгорания: Интенсивный нагрев проводки и самого двигателя.
Очевидно, что такая ситуация недопустима в любой профессиональной инсталляции. Наша задача как инженеров — спроектировать и реализовать систему управления, которая на логическом уровне делает одновременное включение двух реле невозможным. Этот принцип называется взаимной блокировкой (Interlock).
Цель данного урока — на практическом примере разработать полный, надежный и безопасный поток (Flow) в Node-RED для управления реверсивным двигателем штор. Мы создадим программную блокировку, которая будет гарантировать, что команда на движение «Вниз» не сможет быть выполнена, пока двигатель движется «Вверх», и наоборот. Кроме того, мы добавим дополнительный уровень защиты от нештатных ситуаций, таких как отказ датчиков конечного положения.
---
Логика и состояния: Проектирование конечного автомата (State Machine)
Прежде чем написать хотя бы одну строку кода или соединить два узла в Node-RED, профессиональный инженер проектирует логику. Для управления устройствами со сложным поведением, как наш реверсивный двигатель, наилучшим инструментом проектирования является конечный автомат (или State Machine). Этот подход позволяет формально описать все возможные состояния устройства и все события, которые вызывают переходы между этими состояниями.
> 💡 Подсказка: Проектирование логики на бумаге или в виде диаграммы перед реализацией в Node-RED — ключевой этап, который помогает избежать 90% логических ошибок, упрощает отладку и делает систему предсказуемой.
Давайте определим состояния и события для нашего двигателя штор.
Ключевые понятия
- Состояние (State): Четко определенный режим работы устройства в данный момент времени.
- Событие (Event): Внешнее или внутреннее воздействие, которое может изменить состояние устройства (например, нажатие кнопки, команда по MQTT, срабатывание датчика).
- Переход (Transition): Процесс смены одного состояния на другое при наступлении определенного события.
Состояния нашего двигателя
Наш двигатель может находиться в одном из трех основных состояний:
События и переходы
Теперь опишем события, которые инициируют переходы между состояниями:
- Команда `UP`: Пользователь нажал кнопку «Вверх» или отправил команду по сети.
- Команда `DOWN`: Пользователь нажал кнопку «Вниз».
- Команда `STOP`: Пользователь нажал кнопку «Стоп» или сработал таймер безопасности.
- Событие `END_SWITCH`: Штора достигла крайнего верхнего или нижнего положения, и сработал физический концевой выключатель. Это событие также должно переводить двигатель в состояние `stopped`.
Диаграмма переходов состояний
Визуализируем нашу логику. Это можно сделать в виде таблицы, которая наглядно показывает, что происходит в каждом состоянии при каждом событии.
| Текущее состояние | Событие | Новое состояние | Действие |
| :---------------- | :------------- | :-------------- | :---------------------------------- |
| `stopped` | Команда `UP` | `moving_up` | Включить реле "Вверх" |
| `stopped` | Команда `DOWN` | `moving_down` | Включить реле "Вниз" |
| `stopped` | Команда `STOP` | `stopped` | Ничего не делать (уже остановлен) |
| `moving_up` | Команда `UP` | `moving_up` | Игнорировать (уже движется вверх) |
| `moving_up` | Команда `DOWN` | `moving_up` | БЛОКИРОВАТЬ! (Интерлок) |
| `moving_up` | Команда `STOP` | `stopped` | Выключить реле "Вверх" |
| `moving_up` | `END_SWITCH` | `stopped` | Выключить реле "Вверх" |
| `moving_down` | Команда `UP` | `moving_down` | БЛОКИРОВАТЬ! (Интерлок) |
| `moving_down` | Команда `DOWN` | `moving_down` | Игнорировать (уже движется вниз) |
| `moving_down` | Команда `STOP` | `stopped` | Выключить реле "Вниз" |
| `moving_down` | `END_SWITCH` | `stopped` | Выключить реле "Вниз" |
Эта таблица — наш детальный план для реализации логики в Node-RED. Мы точно знаем, как система должна себя вести в любой ситуации.
---
Реализация взаимной блокировки в Node-RED
Теперь, когда у нас есть четкий логический проект, мы можем приступить к его реализации. Центральным элементом нашего потока будет узел `Function`, внутри которого мы воплотим логику конечного автомата. Для хранения текущего состояния двигателя мы будем использовать переменные потока, как это было рассмотрено ранее.
> 🔗 Связанный материал: Мы подробно разбирали работу с переменными потока в уроке о хранении состояния (`COURSE-05-M03-L02`). Обязательно освежите знания перед тем, как продолжить.
Шаг 1: Хранение состояния
Для хранения состояния (`stopped`, `moving_up`, `moving_down`) мы будем использовать переменную потока (flow context). Это позволяет изолировать состояние нашего двигателя в пределах одного потока, не загрязняя глобальное пространство имен.
При старте потока (например, с помощью узла `Inject` с опцией `inject once after 0.1 seconds`), мы должны инициализировать наше состояние:
// В узле Function, подключенном к инициализирующему Inject
flow.set('motor_state', 'stopped');
node.status({fill:"grey", shape:"dot", text:"stopped"});
return null; // Сообщение дальше не идет
Шаг 2: Обработка входящих команд
Команды могут поступать из разных источников: физические кнопки, подключенные к дискретным входам контроллера, или сообщения по MQTT из мобильного приложения. Важно привести их к единому формату в соответствии с контрактом сообщения.
Наш контракт будет выглядеть так:
// msg.payload для управления двигателем
{
"command": "UP" // или "DOWN", или "STOP"
}
Шаг 3: Основной узел логики (State Machine)
Вся логика из нашей таблицы состояний будет реализована в одном узле `Function`. Этот узел будет иметь один вход и три выхода для Mожно использовать и один выход, но три выхода делают поток более наглядным:
- Выход 1: Сообщения для запуска движения «Вверх».
- Выход 2: Сообщения для запуска движения «Вниз».
- Выход 3: Сообщения для остановки движения.
Ниже приведен полный код для этого узла.
// Получаем текущее состояние из контекста. Если оно не установлено, считаем 'stopped'.
const currentState = flow.get('motor_state') || 'stopped';
// Получаем команду из входящего сообщения.
const command = msg.payload.command;
// Инициализируем переменные для новых сообщений на каждый выход.
// Они будут отправлены, только если переход разрешен.
let msgUp = null;
let msgDown = null;
let msgStop = null;
// Реализуем логику конечного автомата с помощью switch
switch (currentState) {
case 'stopped':
if (command === 'UP') {
flow.set('motor_state', 'moving_up');
node.status({ fill: "blue", shape: "dot", text: "Moving UP" });
msgUp = { payload: { command: "START_UP" } };
} else if (command === 'DOWN') {
flow.set('motor_state', 'moving_down');
node.status({ fill: "yellow", shape: "dot", text: "Moving DOWN" });
msgDown = { payload: { command: "START_DOWN" } };
}
// Если команда 'STOP' или любая другая, ничего не делаем.
break;
case 'moving_up':
if (command === 'STOP') {
flow.set('motor_state', 'stopped');
node.status({ fill: "grey", shape: "dot", text: "stopped" });
msgStop = { payload: { command: "STOP_ALL" } };
} else if (command === 'DOWN') {
// ВЗАИМНАЯ БЛОКИРОВКА (INTERLOCK)
// Команда 'DOWN' пришла, когда мы движемся вверх. Блокируем.
node.warn("Interlock triggered: 'DOWN' command ignored while moving up.");
node.status({ fill: "red", shape: "ring", text: "Interlock!" });
}
// Команду 'UP' просто игнорируем, т.к. уже движемся вверх.
break;
case 'moving_down':
if (command === 'STOP') {
flow.set('motor_state', 'stopped');
node.status({ fill: "grey", shape: "dot", text: "stopped" });
msgStop = { payload: { command: "STOP_ALL" } };
} else if (command === 'UP') {
// ВЗАИМНАЯ БЛОКИРОВКА (INTERLOCK)
// Команда 'UP' пришла, когда мы движемся вниз. Блокируем.
node.warn("Interlock triggered: 'UP' command ignored while moving down.");
node.status({ fill: "red", shape: "ring", text: "Interlock!" });
}
// Команду 'DOWN' просто игнорируем.
break;
}
// Отправляем сообщения на соответствующие выходы.
// Если переход не был разрешен, соответствующая переменная останется null.
return [msgUp, msgDown, msgStop];
Этот код в точности реализует нашу диаграмму состояний, обеспечивая надежную взаимную блокировку. Обратите внимание на использование `node.status()` для визуальной обратной связи и `node.warn()` для логирования опасных попыток нарушения блокировки.
---
Защита от перегрузки: Добавление таймаута безопасности
Наша система уже защищена от логических ошибок, но что насчет физических отказов? Самый частый сценарий — отказ или «залипание» конечного выключателя. В этом случае двигатель, достигнув крайнего положения, не получит команду `STOP`. Он продолжит работать, пытаясь двигать штору дальше, что вызовет его перегрев, износ механики и, в конечном счете, поломку.
Для защиты от таких ситуаций мы должны добавить таймаут безопасности, также известный как сторожевой таймер (Watchdog Timer). Его задача — принудительно остановить двигатель, если он работает дольше определенного, заведомо избыточного времени.
> ⚠️ Внимание: Всегда устанавливайте таймаут безопасности с запасом. Его значение должно быть на 15-20% больше максимального реального времени работы механизма от одного края до другого. Это критически важный элемент защиты оборудования.
Для реализации этого механизма идеально подходит узел `Trigger`.
Конфигурация сторожевого таймера
* Send: `Nothing` (или выбрать опцию для немедленной отправки исходного сообщения). Мы будем использовать два узла Trigger, по одному на каждое направление, для чистоты схемы. Либо один, но с более сложной логикой. Давайте рассмотрим простой вариант с двумя.
* then wait for: `30 seconds` (например, если полное открытие штор занимает 25 секунд).
* then send: `{"payload": {"command": "STOP"}}` (сообщение, которое инициирует остановку).
* Handling incoming messages: Выбрать `Extend delay if new message arrives`.
* Когда на узел `Trigger` приходит сообщение `START_UP`, он запускает 30-секундный таймер.
* Если в течение этих 30 секунд приходит штатная команда `STOP` (от кнопки или концевого выключателя), мы должны сбросить таймер, чтобы он не сработал ложно. Для этого нужно отправить на тот же узел `Trigger` сообщение со свойством `msg.reset = true`.
* Если же за 30 секунд сброса не произошло (что означает аварию), узел `Trigger` автоматически сгенерирует и отправит на свой выход стоп-сигнал, который остановит двигатель, предотвратив поломку.
Схема потока с Watchdog
// Упрощенная схема для одного направления (UP)
[Function: State Machine] --(Выход 1: START_UP)--> [Trigger: Watchdog 30s] --(немедл.)--> [To Relay UP]
|
+--(через 30с)--> [To STOP Logic]
[Input: Кнопка/Концевик STOP] --> [To STOP Logic] --> [Change: set msg.reset=true] --> [Trigger: Watchdog 30s]
Эта схема гарантирует, что двигатель никогда не будет работать «бесконечно». Даже при полном отказе всех датчиков положения, система сама себя обезопасит.
---
Интеграция с релейными выходами контроллера HI
Мы разработали надежную логику. Последний шаг — связать ее с реальным оборудованием: релейными выходами контроллера HI. В нашей платформе стандартным способом управления периферией является протокол MQTT. Каждому реле соответствует свой уникальный MQTT-топик.
Формирование управляющих сообщений
Наши логические команды (`START_UP`, `START_DOWN`, `STOP_ALL`) должны быть преобразованы в конкретные сообщения для включения и выключения реле. Например, пусть реле 5 управляет движением вверх, а реле 6 — движением вниз.
Контракт сообщения для управления реле HI:- Топик: `hi/control/relay/5/set` (для реле №5)
- Сообщение (payload):
* Выключить: `{"value": false}`
Для этого мы используем узлы `Change` после выходов нашего логического блока.
Практическая схема интеграции
Представим полный путь прохождения сигнала.
* Сообщение `START_UP` поступает на два узла `Change`:
* Первый `Change`: Настроен для включения реле 5. Он устанавливает `msg.topic` в `hi/control/relay/5/set` и `msg.payload` в `{"value": true}`.
Второй `Change`: Настроен для гарантированного выключения* реле 6 (дополнительная защита). Он устанавливает `msg.topic` в `hi/control/relay/6/set` и `msg.payload` в `{"value": false}`.
* Аналогично обрабатывается команда `START_DOWN`: включается реле 6, выключается реле 5.
* Команда `STOP_ALL` выключает оба реле (5 и 6).
Этот узел будет иметь два правила:
{
"value": true
}
Такой подход, разделяющий логику (`Function`) и формирование команд для железа (`Change`), делает систему гибкой. Если в будущем адреса реле или протокол управления изменятся, нам нужно будет поправить только узлы `Change`, не трогая ядро логики.
---
Резюме: Полная диаграмма потока и ключевые выводы
Сегодня мы прошли полный путь от постановки задачи до создания готового к внедрению, безопасного решения для управления реверсивным двигателем. Давайте взглянем на финальную архитектуру нашего потока и закрепим ключевые принципы.
Финальная диаграмма потока (упрощенно)
// =================== Блок Входных Команд ===================
[Inject: UP] --+
[Inject: DOWN]--+--> [Change: Format Command] --> [Function: State Machine]
[Inject: STOP]--+ | | |
| | +-- (Выход 3: STOP_ALL) --+
| +---- (Выход 2: START_DOWN)-+-> [Trigger: Watchdog]
+------ (Выход 1: START_UP) --+
// ==================== Блок Логики и Защиты =====================
|
v
// ====================== Блок Управления Реле =======================
+--> [Change: Format for Relay 5 ON] --> [MQTT Out]
+--> [Change: Format for Relay 6 OFF]--> [MQTT Out]
+--> [Change: Format for Relay 6 ON] --> [MQTT Out]
+--> [Change: Format for Relay 5 OFF]--> [MQTT Out]
+--> [Change: Format for Relay 5 OFF]--> [MQTT Out]
+--> [Change: Format for Relay 6 OFF]--> [MQTT Out]
Этот поток элегантно решает поставленную задачу, опираясь на три фундаментальных принципа безопасности в автоматизации.
📋 Ключевые выводы:
Что дальше?
Освоенный сегодня паттерн является универсальным. Его можно и нужно применять для любых других сценариев, где есть взаимоисключающие режимы работы:
- Управление шаровыми кранами с электроприводом: Блокировка одновременного открытия подающего и обратного клапана в одном контуре.
- Насосные группы: Блокировка одновременного включения основного и резервного насоса.
- Системы вентиляции: Блокировка одновременного включения режимов "нагрев" и "охлаждение".
В следующем уроке мы рассмотрим, как вынести эту сложную логику в переиспользуемый компонент — субпоток (Subflow), чтобы применять ее в разных частях вашего проекта, не копируя десятки узлов каждый раз.