Шаблон 'Включено/Выключено' (On/Off)
Введение в шаблон 'Включено/Выключено' (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`.
// Получаем текущее состояние из контекста потока.
// Если переменной '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".
Анализ работы потока
- Входное сообщение (от `inject`):
{
"payload": 1678886400000,
"_msgid": "a1b2c3d4.e5f6g7"
}
Содержимое `msg.payload` на этом этапе не имеет значения, так как мы используем `inject` только как триггер.
- Выходное сообщение (из `function` после первого нажатия):
{
"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).
Настройка потока
* 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" и сохраните.
* 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.payload` равно `true`, он отправляет Slave-устройству с ID `10` команду на запись значения `0xFF00` в Coil с адресом `0` (включить).
- Если `msg.payload` равно `false`, он отправляет команду на запись значения `0x0000` в тот же Coil (выключить).
Таким образом, наша абстрактная логика теперь напрямую управляет физическим реле.
Пример `msg` на входе `Modbus-Write`:{
"payload": true
}
Узел `Modbus-Write` сам преобразует это в низкоуровневую Modbus-команду. Никаких дополнительных преобразований в узле `function` не требуется, что подтверждает правильность выбора `boolean` как основного типа данных для состояний.
---
Обработка обратной связи и синхронизация состояния
Отправка команды — это только половина дела. Надежная система должна знать, была ли команда выполнена, и каково реальное состояние устройства. Что если реле не сработало из-за сбоя питания на модуле или обрыва связи? Без обратной связи ваш пользовательский интерфейс будет показывать, что свет включен, хотя на самом деле это не так.
Ключевой принцип здесь — разделение потоков команд и состояний.
- Команда (`.../set`): Поток, который мы только что создали, отвечает за отправку команд.
- Состояние (`.../status`): Отдельный, независимый поток должен периодически опрашивать устройство, чтобы узнать его фактическое состояние, и обновлять интерфейс.
Предотвращение зацикливания с помощью `rbe`
Представим ситуацию:
Для предотвращения таких циклов используется узел `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.
Модифицированный код для узла "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;
Как работает обновленная система:
Такой подход гарантирует, что логика всегда оперирует фактическим состоянием устройства, а система является самосинхронизирующейся.
---
Итоги и лучшие практики
В этом уроке мы рассмотрели один из самых важных шаблонов автоматизации — 'Включено/Выключено' (On/Off). Мы научились реализовывать его с сохранением состояния при помощи контекста потока Node-RED и интегрировать с реальным оборудованием по протоколу Modbus RTU с использованием Modbus Coils (FC5).
Ключевым шагом к созданию по-настоящему надежной системы стало внедрение механизма обратной связи (Feedback Loop) и синхронизации состояния. Мы разделили потоки команд и статусов и использовали узел `rbe` (Report-by-Exception) для предотвращения циклов и оптимизации нагрузки.
Лучшие практики для шаблона On/Off:
- Стандартизируйте состояние: Используйте `boolean` (`true`/`false`) в качестве основного формата состояния внутри логики Node-RED.
- Изолируйте логику: Храните состояние устройства в `flow context`, а не в `global context`, чтобы избежать конфликтов и упростить отладку.
- Комментируйте код: Всегда добавляйте комментарии в узлы `function`, объясняя, что делает каждая часть кода. Это неоценимо для будущего обслуживания.
- Давайте осмысленные имена: Называйте узлы и переменные так, чтобы их назначение было понятно без изучения кода (например, `flow.get('light_kitchen_state')` вместо `flow.get('s1')`).
- Обеспечьте персистентность: Как мы обсуждали в уроке про безопасную перезагрузку контроллера, настройте сохранение контекста Node-RED в файловую систему. Это гарантирует, что система восстановит правильные состояния всех устройств после сбоя питания.
Что дальше?
Теперь, когда мы освоили базовый двухпозиционный шаблон, мы готовы перейти к более динамичным сценариям. В следующем уроке мы разберем шаблон 'Pulse' (Импульс), который необходим для управления устройствами, требующими короткого сигнала для активации, такими как воротные механизмы, шлагбаумы или некоторые модели дверных замков.