ГлавнаяАкадемияИсполнительные устройства: интерлоки, таймауты → Концепция взаимной блокировки (Interlock)

Концепция взаимной блокировки (Interlock)

Урок · Исполнительные устройства: интерлоки, таймауты · 30 мин · theory

Что такое взаимная блокировка (Interlock)?

> 💡 Подсказка: Термин 'interlock' пришел из промышленной автоматики, где он используется для предотвращения повреждения дорогостоящего оборудования и обеспечения безопасности персонала.

Взаимная блокировка (Interlock) — это логический или физический механизм, который предотвращает одновременное выполнение двух или более конфликтующих операций. Основная задача интерлока — обеспечить, чтобы система находилась только в одном из разрешенных состояний в любой момент времени, исключая неопределенные или опасные ситуации.

Простейшая и самая наглядная аналогия — это тамбур-шлюз в банке или на входе в чистое помещение. Этот шлюз состоит из двух дверей. Правила его работы таковы: вы не можете открыть вторую дверь, пока не закрыта первая. Этот простой механизм гарантирует, что между двумя зонами никогда не будет прямого, сквозного прохода. Двери "взаимно блокируют" друг друга.

В контексте умного дома или автоматизации зданий этот принцип приобретает критическое значение для трех аспектов:

  • Безопасность. Блокировка предотвращает ситуации, опасные для жизни человека или сохранности имущества. Классический пример — запрет подачи напряжения от резервного генератора в общую сеть, когда в ней уже есть напряжение от города. Это предотвращает аварию и поражение электрическим током ремонтного персонала.
  • Надежность. Интерлоки защищают исполнительные устройства (актуаторы) от повреждений. Например, одновременная подача команд «Вперед» и «Назад» на реверсивный двигатель может вызвать короткое замыкание в обмотках и его выход из строя.
  • Предсказуемость. Система с правильно настроенными блокировками ведет себя ожидаемо. Она не пытается одновременно нагревать и охлаждать помещение, что делает ее логику понятной для пользователя и экономит ресурсы.
  • Существует два фундаментальных типа блокировок, которые должен знать каждый инсталлятор:

    | Тип блокировки | Описание | Преимущества | Недостатки |

    | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------- |

    | Аппаратная | Реализуется физически, с помощью электрических соединений. Например, силовая цепь одного контактора пропускается через нормально закрытый (NC) контакт другого. | Максимальная надежность. Работает независимо от ПО, контроллера и сбоев в его логике. | Негибкость. Логику нельзя изменить без физической перекоммутации в щите. |

    | Программная | Реализуется на уровне логики контроллера. В нашей экосистеме — это потоки в Node-RED, использующие переменные для отслеживания состояний. | Гибкость. Логику можно легко изменить, адаптировать и усложнить в любой момент. | Зависимость от ПО. Требует тщательного программирования и тестирования. |

    Для критически важных систем, связанных с безопасностью жизни или дорогостоящим оборудованием (например, система ввода резервного питания), всегда используется аппаратная блокировка. Программная блокировка может служить дополнительным, вторым контуром защиты, но никогда не должна быть единственной. Для большинства бытовых задач, таких как управление шторами или климатом, гибкости и надежности правильно реализованной программной блокировки более чем достаточно.

    ---

    Примеры использования в умном доме: от штор до вентиляции

    Концепция взаимной блокировки находит широкое применение в повседневных задачах автоматизации. Понимание этих сценариев поможет вам проектировать более умные и надежные системы.

    Управление моторами (шторы, ворота, жалюзи)

    Это самый распространенный случай, где интерлок абсолютно необходим. Реверсивный двигатель переменного тока, используемый в приводах штор или роллет, имеет две обмотки: одну для движения «вверх» (UP), другую — «вниз» (DOWN). Напряжение 230В подается на одну из обмоток для запуска движения в соответствующем направлении.

    1. Запрет одновременного включения: Система должна гарантировать, что реле, отвечающее за движение «вверх», не может быть включено, если уже включено реле «вниз» (и наоборот).

    2. Пауза при реверсе: При смене направления движения (например, с «вверх» на «вниз») необходимо ввести обязательную паузу (обычно 0.5–1 секунда). Это гарантирует, что первое реле успело разомкнуться, и остаточные токи в двигателе затухли, прежде чем включится второе реле.

    Климат-контроль (HVAC)

    В системах климат-контроля часто используются раздельные устройства для нагрева и охлаждения. Например, теплый пол или радиаторы отопления работают на обогрев, а кондиционер — на охлаждение.

    Логика «окно открыто»

    Это классический пример интерлока, направленного на энергосбережение.

    Управление электропитанием (Автоматический Ввод Резерва, АВР)

    Это наиболее критичный с точки зрения безопасности сценарий.

    ---

    Практика: Реализация программного интерлока в Node-RED

    Программный интерлок — это мощный и гибкий инструмент, который легко реализуется штатными средствами Node-RED. Основой для него служат контекстные переменные.

    > ⚠️ Внимание: При некорректной реализации возможны 'состояния гонки' (race conditions), когда две команды приходят почти одновременно и успевают "проскочить" до установки флага блокировки. Всегда предусматривайте механизм сброса блокировки, чтобы система не 'зависла' в заблокированном состоянии.

    Основным инструментом для создания программного замка является контекст потока (flow context). Это хранилище переменных, доступное для всех узлов в пределах одной вкладки (потока) в редакторе Node-RED. В отличие от глобального контекста, это позволяет создавать независимые блокировки для разных устройств.

    Структура потока для интерлока

    Типичный поток, реализующий блокировку, состоит из следующих шагов:

  • Вход (Команда): Узел `mqtt in`, `http in` или `inject` принимает команду от пользователя или другой системы. Например, сообщение с `msg.payload = "UP"`.
  • Проверка замка (Guard): Узел `Switch` проверяет значение флага блокировки, хранящегося в контексте. Например, он проверяет, равно ли `flow.motor_is_busy` значение `true`.
  • Ветвление логики:
  • * Если замок открыт (`false`): Сообщение проходит на основной выход узла `Switch`.

    * Если замок закрыт (`true`): Сообщение уходит на второй выход (или просто отбрасывается), где может быть зарегистрировано в логе.

  • Установка замка (Lock): Сразу после успешной проверки, узел `Change` устанавливает флаг блокировки в `true`. Например, `Set flow.motor_is_busy to true`. Это крайне важный шаг, который предотвращает обработку следующих команд.
  • Выполнение действия: Сообщение с командой отправляется на исполнительный узел, например, `rpi gpio out` для управления реле.
  • Снятие замка (Unlock): После завершения операции необходимо снять блокировку. Это можно сделать несколькими способами:
  • * По сигналу от другого датчика (например, концевого выключателя).

    * С помощью узла `Trigger`, который отправит сообщение о снятии блокировки через заданное время.

    * С помощью `setTimeout` внутри узла `Function`.

    Пошаговая логика в Node-RED

    Рассмотрим на простом примере.

    Задача: Защитить некий механизм, который активируется на 5 секунд. Пока он активен, повторные команды должны игнорироваться.
    // ASCII-схема потока
    

    [inject: "START"] -> [switch: "is_busy?"] --(false)--> [change: Lock] -> [debug: "ACTION!"] -> [trigger: 5s] -> [change: Unlock]

    |

    +----------------(true)----> [debug: "BUSY!"]

    Настройка узлов:
  • Узел `inject`: Отправляет полезную нагрузку `START`.
  • Узел `switch` ("is_busy?"):
  • * Property: `flow.is_busy`

    * Правило 1: `is false` или `is not true`. Выход 1.

    * Правило 2: `otherwise`. Выход 2.

  • Узел `change` ("Lock"):
  • * Действие: `Set`

    * Property: `flow.is_busy`

    * To: `true` (boolean)

  • Узел `debug` ("ACTION!"): Выводит сообщение о начале действия. Здесь мог бы быть узел управления реле.
  • Узел `trigger` ("5s"):
  • * Send: `Ничего` (Nothing)

    * Then wait for: `5` seconds

    * Then send: `{"payload": "done"}` (или любая другая полезная нагрузка)

  • Узел `change` ("Unlock"):
  • * Действие: `Set`

    * Property: `flow.is_busy`

    * To: `false` (boolean)

  • Узел `debug` ("BUSY!"): Подключен ко второму выходу `switch`, сигнализирует об отклоненной команде.
  • Этот простой, но мощный паттерн является основой для всех программных блокировок. Он гарантирует, что между `Lock` и `Unlock` никакая другая команда `START` не будет обработана.

    ---

    Пример: Блокировка MQTT-команд для реверсивного двигателя

    Рассмотрим полноценный практический пример для управления рулонной шторой.

    Сценарий:

    > 💡 Подсказка: Используйте `setTimeout` внутри узла `function`, чтобы автоматически снимать блокировку по истечении времени. Это надежный способ предотвратить 'вечную' блокировку, если команда сброса (например, от концевика) не будет получена.

    Код потока для импорта в Node-RED

    Вы можете скопировать этот JSON и импортировать его в ваш редактор Node-RED (`Меню -> Импорт`), чтобы получить готовый рабочий пример.

    [{"id":"74e6f966.a9c1e8","type":"tab","label":"Shutter Interlock Example","disabled":false,"info":""},{"id":"a3b4c1d2.e5a3c","type":"mqtt in","z":"74e6f966.a9c1e8","name":"CMD: hi/shutter1/cmd","topic":"hi/shutter1/cmd","qos":"2","datatype":"json","broker":"YOUR_MQTT_BROKER_ID","x":210,"y":200,"wires":[["f8d2e3f4.12345"]]},{"id":"f8d2e3f4.12345","type":"function","z":"74e6f966.a9c1e8","name":"Shutter Interlock Logic","func":"// Константы\nconst MOTION_TIME = 15000; // 15 секунд в миллисекундах\nconst REVERSE_DELAY = 500;   // 0.5 секунды пауза перед реверсом\n\n// 1. Проверяем, не занят ли мотор\nlet isLocked = flow.get('shutter_locked') || false;\n\n// 2. Получаем новую команду (UP, DOWN, STOP)\nlet command = msg.payload.state;\nlet lastCommand = flow.get('last_command') || \"STOP\";\n\n// Таймер, который сейчас активен\nlet activeTimer = flow.get('active_timer');\n\n// --- Логика обработки команд ---\n\n// Команда STOP всегда должна выполняться\nif (command === \"STOP\") {\n    if (activeTimer) {\n        // Останавливаем предыдущий таймер\n        clearTimeout(activeTimer);\n    }\n    flow.set('shutter_locked', false); // Снимаем блокировку\n    flow.set('last_command', 'STOP');\n    node.status({ fill: \"grey\", shape: \"dot\", text: \"Stopped\" });\n    // Отправляем команду на выключение ОБОИХ реле\n    return [[{payload: false}, {payload: false}]];\n}\n\n// Если пришла команда, когда мотор занят, и это та же самая команда - игнорируем\nif (isLocked && command === lastCommand) {\n    node.warn(`Ignored command '${command}' - motor is already moving.`);\n    return null;\n}\n\n// Сценарий реверса: если мотор движется, и пришла обратная команда\nif (isLocked && command !== lastCommand) {\n    node.warn(`Reversing... pausing for ${REVERSE_DELAY}ms`);\n    if (activeTimer) {\n        clearTimeout(activeTimer);\n    }\n    // Сначала останавливаем мотор\n    // msg1 для реле UP, msg2 для реле DOWN\n    msg1 = { payload: false, topic: \"relay_up\" };\n    msg2 = { payload: false, topic: \"relay_down\" };\n    \n    // Через паузу запускаем обратное движение\n    setTimeout(() => {\n        let newMsg = { payload: { state: command } };\n        node.send(newMsg); // Отправляем сообщение самому себе для повторной обработки\n    }, REVERSE_DELAY);\n    \n    return [[msg1, msg2]]; // Немедленно отправляем команду STOP\n}\n\n\n// --- Основная логика, если мотор не занят ---\n\nif (!isLocked) {\n    flow.set('shutter_locked', true); // БЛОКИРУЕМ\n    flow.set('last_command', command);\n\n    let msg1 = { payload: false }; // Реле UP\n    let msg2 = { payload: false }; // Реле DOWN\n\n    if (command === \"UP\") {\n        msg1.payload = true;\n        node.status({ fill: \"yellow\", shape: \"ring\", text: \"Moving UP...\" });\n    } else if (command === \"DOWN\") {\n        msg2.payload = true;\n        node.status({ fill: \"yellow\", shape: \"ring\", text: \"Moving DOWN...\" });\n    } else {\n        // Неизвестная команда\n        flow.set('shutter_locked', false); // Снимаем ошибочную блокировку\n        return null;\n    }\n    \n    // Устанавливаем таймер для автоматической остановки и снятия блокировки\n    let timer = setTimeout(() => {\n        node.send([[{payload: false}, {payload: false}]]); // Отправляем команду STOP на выходы\n        flow.set('shutter_locked', false);\n        flow.set('last_command', 'STOP');\n        node.status({ fill: \"green\", shape: \"dot\", text: \"Idle\" });\n    }, MOTION_TIME);\n    \n    flow.set('active_timer', timer); // Сохраняем таймер, чтобы его можно было отменить\n\n    // Узел Function имеет 2 выхода\n    return [[msg1, msg2]];\n}\n\nreturn null; // На всякий случай","outputs":1,"noerr":0,"initialize":"","finalize":"","x":470,"y":200,"wires":[["c1a2b3d4.e5f6g","h7i8j9k0.l1m2n"]]},{"id":"c1a2b3d4.e5f6g","type":"rpi-gpio out","z":"74e6f966.a9c1e8","name":"RL-01 (UP)","pin":"22","set":true,"level":"0","freq":"","out":"out","x":710,"y":160,"wires":[]},{"id":"h7i8j9k0.l1m2n","type":"rpi-gpio out","z":"74e6f966.a9c1e8","name":"RL-02 (DOWN)","pin":"24","set":true,"level":"0","freq":"","out":"out","x":720,"y":240,"wires":[]}]
    

    Детальный разбор логики в узле `Function`

    Узел `function` "Shutter Interlock Logic" является сердцем этой системы. Он имеет два выхода: первый для управления реле `UP`, второй — для реле `DOWN`.

    * `shutter_locked` (boolean): наш главный флаг-замок.

    * `last_command` (string): хранит последнюю выполненную команду (`UP`, `DOWN`, `STOP`).

    * `active_timer`: идентификатор таймера `setTimeout`, чтобы его можно было отменить командой `STOP`.

    1. Отменяет предыдущий таймер.

    2. Немедленно отправляет команду на выключение обоих реле (остановка).

    3. Запускает короткий таймер на `REVERSE_DELAY` (500 мс).

    4. По истечении этой паузы, узел отправляет исходное сообщение самому себе (`node.send(...)`), чтобы запустить движение в новом направлении уже по стандартной логике.

    1. Немедленно устанавливает `flow.set('shutter_locked', true)`.

    2. Формирует сообщения для включения нужного реле (например, `msg1.payload = true`, `msg2.payload = false`).

    3. Запускает `setTimeout` на 15 секунд. По истечении этого времени он отправит команду на выключение реле и снимет блокировку.

    4. Сохраняет ID таймера в `flow.context`, чтобы команда `STOP` могла его прервать.

    Этот пример демонстрирует надежный, отказоустойчивый программный интерлок, который корректно обрабатывает повторные команды, реверс и остановку движения.

    ---

    Итоги и лучшие практики

    В этом уроке мы рассмотрели одну из фундаментальных концепций в автоматизации — взаимную блокировку. Правильное применение интерлоков отделяет любительскую поделку от профессиональной, надежной и безопасной инсталляции.

    > 🔗 Связанный материал: Более сложные аппаратные блокировки и релейная логика, используемые для критических систем, будут рассмотрены в уроке `COURSE-05-M04-L02`.

    Подведем итоги и сформулируем ключевые правила, которые необходимо соблюдать при проектировании блокировок:

    Лучшие практики реализации программных интерлоков:

  • Всегда иметь механизм сброса. У "замка" всегда должен быть "ключ". Блокировка не может быть вечной. Она снимается либо по завершении операции, либо по тайм-ауту, либо командой "стоп".
  • Использовать таймаут как "сторожевой таймер" (Watchdog). Что, если концевой выключатель шторы сломается? Мотор будет работать до сгорания. Гарантированный сброс блокировки и отключение питания по `setTimeout` — ваша страховка от таких сбоев.
  • Документировать логику. Через полгода вы сами не вспомните, почему флаг `flow.lock_1` блокирует насос. Оставляйте комментарии в узлах `function` и в узлах `comment` на холсте Node-RED. Описывайте, какой флаг за что отвечает и почему.
  • Изолировать контекст. Используйте `flow.context` вместо `global.context`. Это гарантирует, что логика блокировки для шторы в гостиной не повлияет случайно на логику ворот гаража.
  • Обеспечить корректную инициализацию. Как мы рассматривали в уроке про безопасную перезагрузку контроллера, при старте системы все флаги блокировок должны быть сброшены в исходное состояние (`false`). Для этого можно использовать узел `inject` с активированной опцией "Inject once after 0.1 seconds, then".
  • Что дальше?

    Теперь, когда вы понимаете концепцию и умеете реализовывать программные блокировки, в следующем уроке мы перейдем к более сложной теме: аппаратные интерлоки на базе релейной логики. Вы научитесь собирать схемы, которые обеспечивают максимальный уровень надежности для критически важных исполнительных устройств.