ГлавнаяАкадемияИсполнительные устройства: интерлоки, таймауты → Шаблон 'Включено/Выключено' (On/Off)

Шаблон 'Включено/Выключено' (On/Off)

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

Введение в шаблон 'Включено/Выключено' (On/Off)

Шаблон 'Включено/Выключено' (On/Off) — это наиболее фундаментальный и распространенный паттерн управления в системах автоматизации. Он описывает управление любым объектом, который может находиться только в двух взаимоисключающих состояниях. Этот бинарный подход является краеугольным камнем для построения более сложных сценариев.

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

| Тип данных | Состояние 'Включено' | Состояние 'Выключено' | Область применения и комментарии |

| :--------- | :-------------------- | :--------------------- | :-------------------------------------------------------------------------------------------------------------------------- |

| Boolean| `true` | `false` | Рекомендуемый стандарт. Максимальная совместимость с узлами Node-RED, однозначность, отсутствие ошибок при преобразовании типов. |

| Number | `1` | `0` | Часто используется в протоколах низкого уровня, таких как Modbus. Требует преобразования в/из boolean в логике. |

| String | `"ON"` / `"on"` | `"OFF"` / `"off"` | Удобно для читаемости в MQTT-сообщениях, но наиболее подвержено ошибкам (регистр, опечатки). Требует валидации. |

> 💡 Подсказка: Важно стандартизировать формат состояний во всей системе. Рекомендуется использовать boolean `true`/`false` для максимальной совместимости с узлами Node-RED и минимизации ошибок при преобразовании типов. Преобразование в другие форматы (например, `1`/`0` для Modbus) следует производить непосредственно перед отправкой команды на устройство.

Классические примеры применения шаблона On/Off включают:

Важно отличать данный шаблон от других, которые будут рассмотрены в последующих уроках. Импульсный шаблон (Pulse) не имеет устойчивого состояния "включено", а лишь генерирует короткий сигнал для активации устройства (например, открытие ворот). Временной шаблон (Timed) включает устройство на заранее определенный промежуток времени, после чего оно автоматически выключается (например, вентиляция в санузле). Шаблон On/Off, в свою очередь, является управлением с сохранением состояния (Stateful): устройство остается во включенном или выключенном состоянии до тех пор, пока не получит явную команду на его изменение.

---

Реализация логики On/Off в Node-RED

Для реализации шаблона On/Off необходимо создать логику, которая хранит текущее состояние устройства и инвертирует его при получении управляющего сигнала. Идеальным инструментом для этого является контекст потока (Flow Context) в Node-RED. Контекст потока позволяет хранить переменные, доступные только в пределах одной вкладки (flow), что изолирует логику управления одним устройством от других.

> 🔗 Связанный материал: Мы предполагаем, что вы уже знакомы с основами Node-RED. Если нет, обратитесь к уроку COURSE-02-M01-L04 'Основы работы в среде Node-RED'.

Давайте создадим базовый поток для реализации переключения On/Off.

Пошаговая инструкция

  • Создайте базовый поток. Перетащите на холст три узла:
  • * `inject` — будет имитировать нажатие кнопки.

    * `function` — здесь будет находиться наша основная логика.

    * `debug` — для отслеживания результата.

    Соедините их последовательно: `inject` -> `function` -> `debug`.

  • Настройте узел `inject`. Дважды щелкните по нему. В его настройках ничего менять не нужно. По умолчанию он отправляет `msg.payload`, равный текущей временной метке, что идеально подходит для простого триггера.
  • Напишите код в узле `function`. Это сердце нашего шаблона. Дважды щелкните по узлу `function` и вставьте следующий JavaScript-код:
  •     // Получаем текущее состояние из контекста потока.

    // Если переменной 'state' еще не существует (первый запуск),

    // присваиваем ей значение по умолчанию 'false' (выключено).

    let state = flow.get('state') || false;

    // Инвертируем состояние: если было true, станет false, и наоборот.

    state = !state;

    // Сохраняем новое состояние обратно в контекст потока.

    // Это важно для следующего вызова.

    flow.set('state', state);

    // Помещаем новое состояние в msg.payload, чтобы передать его дальше по потоку.

    msg.payload = state;

    // Устанавливаем статус узла для наглядной отладки в редакторе.

    if (state === true) {

    node.status({ fill: "green", shape: "dot", text: "ON" });

    } else {

    node.status({ fill: "red", shape: "ring", text: "OFF" });

    }

    // Возвращаем сообщение для дальнейшей обработки.

    return msg;

    Назовите этот узел "Toggle Logic".

  • Разверните и протестируйте. Нажмите кнопку "Deploy". Теперь при каждом нажатии на квадратик слева от узла `inject`, вы будете видеть в панели `debug` поочередно `true` и `false`. Обратите внимание на статус под узлом `function`: он будет меняться с "OFF" на "ON" и обратно, что позволяет визуально отслеживать состояние без окна отладки.
  • Анализ работы потока

        {
    

    "payload": 1678886400000,

    "_msgid": "a1b2c3d4.e5f6g7"

    }

    Содержимое `msg.payload` на этом этапе не имеет значения, так как мы используем `inject` только как триггер.

        {
    

    "payload": true,

    "_msgid": "a1b2c3d4.e5f6g7"

    }

    Логика сработала: начальное состояние было `false`, оно инвертировалось в `true` и было записано в `msg.payload`. Это значение `true` теперь можно использовать для отправки команды на физическое устройство. При следующем нажатии `msg.payload` будет `false`.

    Этот простой поток является универсальным ядром для любого управления по шаблону On/Off. К его выходу мы можем подключать узлы для работы с любыми протоколами: Modbus, MQTT, DALI и др.

    ---

    Пример: управление реле по Modbus RTU

    Теперь давайте применим нашу логику для управления реальным физическим устройством — релейным модулем, подключенным по шине RS-485 и работающим по протоколу Modbus RTU. Мы будем использовать стандартную палитру `node-red-contrib-modbus`.

    > ⚠️ Внимание: При работе с шиной RS-485 убедитесь, что на ней нет двух 'мастеров'. Контроллер HI в данном случае выступает единственным мастером, опрашивающим исполнительные устройства (slaves).

    Как мы уже знаем из урока по протоколу Modbus, управление состоянием реле обычно осуществляется через запись в регистры типа Coil (катушка). Команда на запись в один регистр Coil имеет код функции 5 (FC5).

    Настройка потока

  • Добавьте узел `Modbus-Write`. Найдите его в палитре и поместите на холст после нашего узла `function` ("Toggle Logic").
  • Настройте Modbus-сервер (клиент). Дважды щелкните на узле `Modbus-Write` и в поле `Server` выберите "Add new modbus-client...".
  • * Type: `RTU-BUFFERED`. Этот тип обеспечивает более стабильную работу, буферизуя запросы.

    * Serial Port: Укажите путь к порту RS-485 на контроллере. Обычно это `/dev/ttyUSB0` или `/dev/ttyS1`.

    * Baud Rate, Data Bits, Parity, Stop Bits: Установите эти параметры в точном соответствии с настройками вашего релейного модуля (например, `9600`, `8`, `None`, `1`).

    * Назовите этот сервер, например, "RS485-Bus-1" и сохраните.

  • Настройте узел `Modbus-Write`. Теперь вернемся к настройкам самого узла.
  • * Unit-Id: Укажите Slave ID вашего релейного модуля (например, `10`). Этот адрес должен быть уникальным на шине.

    * FC: Выберите `FC 5: Force Single Coil`. Эта функция записывает состояние `true` или `false` в одну "катушку" (реле).

    * Address: Укажите адрес регистра, отвечающего за нужное реле. Адресация начинается с `0`. Если в документации к модулю указан "Coil 1", то адрес будет `0`. Если "Coil 2", то `1`, и так далее.

    * Name: Дайте узлу осмысленное имя, например, "Управление реле освещения RL1".

    Теперь полный поток выглядит так: `inject` -> `function` ("Toggle Logic") -> `Modbus-Write`.

    Как это работает

    Когда вы нажимаете на `inject`, узел `function` генерирует `msg.payload` со значением `true` или `false`. Это сообщение поступает на вход узла `Modbus-Write`. Узел `Modbus-Write` видит, что `msg.payload` является булевым значением, и автоматически формирует правильный Modbus-запрос:

    Таким образом, наша абстрактная логика теперь напрямую управляет физическим реле.

    Пример `msg` на входе `Modbus-Write`:
    {
    

    "payload": true

    }

    Узел `Modbus-Write` сам преобразует это в низкоуровневую Modbus-команду. Никаких дополнительных преобразований в узле `function` не требуется, что подтверждает правильность выбора `boolean` как основного типа данных для состояний.

    ---

    Обработка обратной связи и синхронизация состояния

    Отправка команды — это только половина дела. Надежная система должна знать, была ли команда выполнена, и каково реальное состояние устройства. Что если реле не сработало из-за сбоя питания на модуле или обрыва связи? Без обратной связи ваш пользовательский интерфейс будет показывать, что свет включен, хотя на самом деле это не так.

    Ключевой принцип здесь — разделение потоков команд и состояний.

    Предотвращение зацикливания с помощью `rbe`

    Представим ситуацию:

  • Вы отправили команду `ON`. Реле включилось.
  • Поток опроса состояния считывает `ON` и отправляет это в UI.
  • Если этот же статус попадет обратно в логику управления, он может вызвать повторную отправку команды `ON`, создавая бесконечный цикл и лишнюю нагрузку на шину.
  • Для предотвращения таких циклов используется узел `rbe` (Report by Exception). Он пропускает сообщение дальше по потоку только в том случае, если его значение (`msg.payload`) отличается от предыдущего полученного сообщения.

    > 💡 Подсказка: Узел `rbe` — один из самых полезных при работе с физическими устройствами. Он пропускает сообщение дальше, только если его `msg.payload` отличается от предыдущего, эффективно отсекая дублирующиеся статусы.

    Пример реализации с обратной связью

    Давайте доработаем наш пример с Modbus.

  • Создайте поток опроса состояния.
  • * `inject` (настроен на периодический запуск, например, каждые 5 секунд).

    * `Modbus-Getter` (настроен на чтение того же Coil-регистра, FC 1: Read Coils).

    * `function` (для приведения ответа к `true`/`false`).

    * `rbe` (режим `block unless value changes`).

    * `debug` или узел для обновления UI.

  • Синхронизируйте контекст. Теперь состояние в `flow.context` нашей логики переключения должно обновляться не самой логикой, а потоком обратной связи.
  • Модифицированный код для узла "Toggle Logic":

    // Мы больше не инвертируем состояние напрямую!
    

    // Мы отправляем команду, противоположную ТЕКУЩЕМУ состоянию.

    // Получаем актуальное состояние, которое обновляется потоком опроса.

    let currentState = flow.get('state') || false;

    // Формируем команду на инвертирование.

    let command = !currentState;

    // Отправляем команду на исполнение.

    msg.payload = command;

    // Статус теперь будет показывать отправленную команду, а не предполагаемое состояние.

    node.status({ fill: "blue", shape: "ring", text: "Cmd: " + (command ? "ON" : "OFF") });

    return msg;

    Как работает обновленная система:
  • Пользователь нажимает кнопку.
  • "Toggle Logic" смотрит на актуальное состояние `flow.state` (которое пришло от устройства) и отправляет противоположную команду (`true` или `false`).
  • `Modbus-Write` исполняет команду.
  • Через несколько секунд `Modbus-Getter` считывает новое, фактическое состояние реле.
  • `rbe` видит, что состояние изменилось (например, с `false` на `true`), и пропускает сообщение.
  • Это новое состояние (`true`) обновляет `flow.state` и статус в пользовательском интерфейсе.
  • При следующем нажатии кнопки "Toggle Logic" увидит, что `flow.state` теперь `true`, и отправит команду `false`.
  • Такой подход гарантирует, что логика всегда оперирует фактическим состоянием устройства, а система является самосинхронизирующейся.

    ---

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

    В этом уроке мы рассмотрели один из самых важных шаблонов автоматизации — 'Включено/Выключено' (On/Off). Мы научились реализовывать его с сохранением состояния при помощи контекста потока Node-RED и интегрировать с реальным оборудованием по протоколу Modbus RTU с использованием Modbus Coils (FC5).

    Ключевым шагом к созданию по-настоящему надежной системы стало внедрение механизма обратной связи (Feedback Loop) и синхронизации состояния. Мы разделили потоки команд и статусов и использовали узел `rbe` (Report-by-Exception) для предотвращения циклов и оптимизации нагрузки.

    Лучшие практики для шаблона On/Off:

    Что дальше?

    Теперь, когда мы освоили базовый двухпозиционный шаблон, мы готовы перейти к более динамичным сценариям. В следующем уроке мы разберем шаблон 'Pulse' (Импульс), который необходим для управления устройствами, требующими короткого сигнала для активации, такими как воротные механизмы, шлагбаумы или некоторые модели дверных замков.