ГлавнаяАкадемияСценарии умного дома: режимы, состояния, приоритеты → Управление устройствами при смене режима (выключить все, активировать охрану)

Управление устройствами при смене режима (выключить все, активировать охрану)

Урок 3 · Сценарии умного дома: режимы, состояния, приоритеты · 30 мин · theory

Введение: От состояний к действиям

На предыдущих уроках мы спроектировали граф состояний (`Home`, `Away`, `Night`), определили триггеры для их смены и создали централизованный Subflow. Эта подпрограмма обновляет глобальную переменную `global.Context.mode` (например, записывая строковое значение `"Away"`), которая теперь служит «единственным источником правды» о текущем режиме дома.

> 💡 Связанный материал: Этот урок является прямым продолжением прошлого занятия, где мы собрали Subflow для безопасного переключения состояний и передачи обновленного режима дальше по потоку (в `msg.payload`) для последующей обработки исполнительными устройствами.

Однако само по себе изменение текстовой переменной не выключает свет и не ставит дом на охрану. Система должна реактивно откликаться на эту смену и выполнять предопределенные наборы действий. Этот урок посвящен ключевому архитектурному паттерну «Реакция на смену состояния», который транслирует абстрактные режимы в физические действия.

Как работает паттерн реактивности

Чтобы превратить логику контроллера в настоящего «дирижера», мы выстроим поток данных (Data Flow) по следующей схеме:

  • Триггер изменений: Получение сигнала об обновлении режима (например, `msg.payload == "Away"`) через `link in` от нашего Subflow.
  • Блок маршрутизации (Router): Использование ноды `switch`, которая работает как железнодорожная стрелка и направляет `msg` в соответствующую ветку логики (ветка "Ушли", ветка "Спим" и т.д.).
  • Формирование команд (State Translation): Использование нод `change` для создания конкретных пакетов данных: команд (action: `turn_off`) или целевых параметров (уставка температуры: `18`).
  • Исполнение (Actuation): Вызов сервисов интеграции (отправка `payload` на физические реле, термостаты или хаб умного дома).
  • Разбираемые сценарии

    Мы соберем ядро маршрутизации, а затем наполним его конкретными аппаратными командами на базе двух классических примеров:

  • Режим `Away` («Никого нет дома»):
  • * Глобальное выключение света (вызов команды `turn_off` для целевой группы `group.all_lights`).

    * Обесточивание розеток второстепенных потребителей (телевизоры, утюги), но с сохранением питания критической инфраструктуры (холодильник, роутер, сервер).

    * Переход климата в энергосбережение (понижение термостатов отопления до 18°C).

    * Активация полного контура охраны (все датчики движения и герконы берутся под мониторинг).

  • Режим `Night` («Ночь»):
  • * Частичное отключение (гасится основной свет, но розетки и медиа-устройства могут оставаться в ждущем режиме).

    * Изменение параметров других сценариев (датчики движения в коридорах начинают активировать «дежурное освещение» с яркостью не более 15% и теплым спектром).

    * Активация частичной охраны периметра (сирена сработает только на открытие внешних дверей/окон, игнорируя движение внутри спален).

    Задача этого урока — создать в Node-RED модульный и масштабируемый флоу. Правильно выстроенная архитектура гарантирует: когда вы купите умные рулонные шторы и захотите закрывать их при уходе из дома, вам потребуется лишь привязать одну новую ноду-действия к готовой ветке `Away`, вообще не трогая код управления режимами.

    Архитектурный паттерн: Реакция на смену состояния

    Вместо создания десятков разрозненных потоков, реагирующих на датчики и интерфейсы отдельно, профессиональный подход строится на паттерне «Единый диспетчер». Диспетчер слушает изменение только одного параметра — состояния глобального режима — и направляет сигнал в нужную ветку исполнения.

    Архитектура потока-диспетчера в Node-RED состоит из трех базовых узлов:

  • Источник (`link in`): Принимает сообщение с выхода вашего Subflow управления режимами. Основная полезная нагрузка содержит текстовое значение нового режима в `msg.payload` (например, `"away"`, `"home"`, `"night"`).
  • Фильтр дубликатов (`rbe` - Report by Exception): Пропускает сообщение дальше только если `msg.payload` изменился с момента последнего прохождения.
  • Конфигурация узла:* Mode: `block unless value changes`, Property: `msg.payload`.

    Зачем это нужно:* Предотвращает цикличное и избыточное выполнение сценариев. Если система случайно дважды пришлет статус `"away"`, тяжеловесный скрипт "выключить все" отработает только один раз.

  • Маршрутизатор (`switch`): Проверяет значение `msg.payload` и направляет поток по соответствующим выходам.
  • Правила (Rules):* `== "home"` (Выход 1), `== "away"` (Выход 2), `== "night"` (Выход 3).

    Таким образом, связка `[Link In: Входящие режимы] -> [rbe: Фильтр изменившихся значений] -> [Switch: Выбор логики]` становится ядром маршрутизации. От каждого выхода узла `switch` будет начинаться ветка, реализующая физические действия для конкретного сценария.

    Шаг 1: Инвентаризация и создание Whitelist (Списка исключений)

    Прежде чем подключать исполнительные узлы к выходу `"away"` (Сценарий «Никого нет дома»), необходимо строго разделить оборудование на управляемое и неприкасаемое. Имплементация массовых отключений без создания списка исключений (Whitelist) — самая частая причина аварий на объектах умного дома.

    В Node-RED этот список реализуется не программными проверками каждого устройства в коде (это перегрузит логику), а на этапе формирования целевых групп: мы группируем и отправляем команды конфигурационно только туда, куда разрешено.

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

    | Устройства для группового управления (Отключаем) | Устройства-исключения (Whitelist - Не трогаем!) |

    | :--- | :--- |

    | Освещение: все группы света (потолочные, бра, торшеры, LED-ленты на фасаде и внутри) | Системы жизнеобеспечения: холодильники, морозильные камеры, системы аква-очистки |

    | Управляемые розетки: утюги, телевизоры, розетки на кухне (рабочий фартук) | Отопление и водоснабжение: циркуляционные насосы, котлы, греющие кабели, бойлеры |

    | Медиасистемы: аудиоресиверы, игровые консоли, усилители | IT-инфраструктура: контроллер умного дома, роутеры, коммутаторы (PoE), NAS/серверы |

    | Климатические системы: перевод кондиционеров, вентиляции и теплых полов в эконом-режим (смена уставки, а не жесткое обесточивание) | Системы безопасности: блок охранной сигнализации, камеры видеонаблюдения, счетчики |

    | Прочее: Мелкая бытовая техника, кофемашины, зарядные устройства | Системы защиты от аварий: датчики протечки, датчики дыма и их контроллеры/электроклапаны |

    Шаг 2: Применение нативных групповых команд

    Утвердив список устройств, подлежащих управлению, необходимо настроить эффективную передачу команд. Отправлять команду каждому из 50-100 устройств по отдельности (создавая 50 узлов `mqtt out` или `knx-device` в Node-RED) — плохая практика. Это перегружает шину данных и создает нечитаемый код.

    Современные протоколы поддерживают групповые команды на уровне железных шлюзов и контроллеров:

    Использование групповых адресов снижает задержки сети с нескольких секунд до миллисекунд и делает поток в Node-RED лаконичным.

    Шаг 3: Управление пусковыми токами (Staggered Execution)

    Даже при использовании эффективных групповых команд возникает аппаратный нюанс. Одновременное отключение или, что еще важнее, включение (при возвращении домой) десятков импульсных блоков питания (LED-драйверы, телевизоры) вызывает мгновенные пусковые токи (Inrush Current), превышающие номинальные в 10-50 раз. Это может привести к "залипанию" контактов реле или выбиванию автоматов в электрощите.

    > 💡 UX/Архитектурное правило:

    > Если аппаратные контроллеры не поддерживают поочередный запуск (staggered start), разделяйте включение/выключение крупных пулов программно, используя узлы `delay`.

    > Пример: Ветка режима `"away"`: `[Выключить свет 1 этажа]` -> Узел `[Delay: 500ms]` -> `[Выключить свет 2 этажа]` -> Узел `[Delay: 500ms]` -> `[Обесточить розеточные группы]`. Это сохранит ресурс аппаратных реле и обеспечит стабильность системы при смене режимов.

    Пример 1: Реакция на режим «Away»

    При переходе в режим «Никого нет дома» (Away) система должна безопасно перевести дом в энергосберегающее и защищенное состояние. От выхода `away` нашего узла определения режима мы направляем поток в узел `function`. Его задача — сформировать пакет команд для различных физических и программных шин.

    Единая генерация команд в узле `function`

    Консолидация логики в одном узле `function` позволяет держать сценарий в чистоте: вместо гирлянды из десятка узлов `change` мы используем один лаконичный скрипт. Этот узел формирует массив сообщений.

    /*
    

    * Сценарий "Никого нет дома" (Away)

    * Генерирует пакет команд для различных подсистем.

    */

    // 1. KNX: Команда выключения всего света (DPT 1.001)

    const knxLightOff = {

    topic: "knx/write/1/1/0", // Групповой адрес "Свет весь выкл"

    payload: false

    };

    // 2. MQTT: Физическое отключение розеток (например, утюг, ТВ)

    const mqttSocketsOff = {

    topic: "home/sockets/all/set", // Групповой топик

    payload: "OFF",

    qos: 1, // Гарантированная доставка критичной команды

    retain: true // Сохраняем последнее целевое состояние для новых устройств

    };

    // 3. Климатика: Перевод термостатов в энергосбережение

    // Детальная настройка климата рассмотрена в Уроке COURSE-06.

    const hvacEcoMode = {

    topic: "home/climate/thermostat/set_mode",

    payload: "eco"

    };

    // 4. Охрана: Постановка на полную охрану

    // Интеграция с системами безопасности описана в Уроке COURSE-10.

    const securityArm = {

    topic: "home/security/set_state",

    payload: {

    "state": "armed_full",

    "trigger": "auto_mode_away" // Передаем контекст для логов

    }

    };

    // ВАЖНО: Чтобы Node-RED отправил несколько отдельных сообщений

    // последовательно из ПЕРВОГО выхода, их необходимо обернуть в массив массивов.

    // Формат: return [[msg1, msg2, msg3, ...]]

    return [[ knxLightOff, mqttSocketsOff, hvacEcoMode, securityArm ]];

    Маршрутизация мульти-шинных команд

    После узла `function` сообщения вылетают одно за другим. Чтобы направить их в нужные интерфейсы (в KNX-шлюз или MQTT-брокер), применяется один из двух подходов:

  • Неявная маршрутизация (в узлах вывода): Узлы вроде `mqtt out` можно настроить так, чтобы целевой топик брался динамически из `msg.topic`, оставив поле Topic в настройках узла пустым. Однако, если KNX-сообщение попадет в MQTT-узел, оно будет ошибочно отправлено в брокер.
  • Явная фильтрация узлом `switch` (Рекомендуется): Выходящий поток пропускается через узел `switch`, который проверяет префикс `msg.topic`:
  • * Правило 1: `включает` `knx/` → направляем на узел `knx-ultimate out`.

    * Правило 2: `включает` `home/` → направляем на узел `mqtt out`.

    Управление задержками (Rate Limiting)

    > ⚠️ Важно: В больших домах одновременное исполнение команды «выключить всё» может привести к срабатыванию автоматов защиты из-за экстратоков размыкания сотен блоков питания (свет, техника) или вызвать переполнение (bus overload) на шине KNX/RS-485.

    >

    > Решение: Между узлом `function` и физическими интерфейсами вывода добавьте узел `delay` в режиме Rate Limit (Ограничение скорости) — например, пропускать не более 5 сообщений в секунду. Это сделает отключение дома плавным (эффект "домино") и безопасным для электросети.

    Альтернативный подход: Разделение по выходам узла `function`

    Если вы предпочитаете визуальную наглядность программированию, вы можете настроить узел `function` на несколько выходов (например, Выход 1 для KNX, Выход 2 для MQTT, Выход 3 для охраны).

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

    // knxLightOff уйдет в 1-й выход, mqttSocketsOff во 2-й, hvacEcoMode в 3-й
    

    return [ knxLightOff, mqttSocketsOff, hvacEcoMode ];

    Это позволяет визуально разделить потоки проводов в редакторе Node-RED и избавиться от фильтрующего узла `switch`, но жестко привязывает код внутри к количеству "дырок" (выходов) самого узла.

    > 💡 Подсказка: Для начала рекомендуется использовать первый подход (генерация массива массивов `[[...]]` + узел `switch`). Он позволяет масштабировать логику без необходимости постоянно перенастраивать выходы узла при добавлении новых подсистем.

    Пример 2: Реакция на режим «Night»

    Сценарий «Ночь» гораздо тоньше, чем «Никого нет дома». Его цель — не полное отключение, а создание комфортной и безопасной среды для сна, что требует частичного управления состоянием устройств и плавных переходов.

    Ключевые отличия от режима «Away»

    > 💡 Подсказка: Для ночного освещения удобно использовать не только диммирование (5-10%), но и изменение цветовой температуры (CCT) в сторону глубоко теплого спектра (<2700K, идеально 2200K) для естественной выработки мелатонина. Подробно работа со светом, включая DALI и KNX, рассмотрена в уроке по освещению.

    Учет UX и локальных исключений (Overrides)

    Переход в режим «Ночь» меняет логику работы локальных автоматизаций:

    Реализация в Node-RED

    Логика для режима «Ночь» создается аналогично режиму «Away» — в ветке, отходящей от выхода `night` узла `switch` (маршрутизатора режимов). Мы используем узел `function` для генерации единого пакета команд.

    ⚠️ Технический нюанс: В Node-RED возврат плоского массива `[msg1, msg2, msg3]` отправит `msg1` на первый выход узла, `msg2` на второй и т.д. Чтобы отправить несколько сообщений последовательно в один выход (например, в универсальный MQTT-брокер), массив сообщений нужно обернуть в еще один массив: `[[msg1, msg2, msg3]]`.

    /*
    

    * Сценарий "Ночь" (Night)

    * Генерирует команды частичного управления и охраны периметра.

    */

    // 1. Команда выключения ОСНОВНОГО света (KNX)

    const mainLightOff = {

    topic: "knx/write/1/1/1", // Групповой адрес "Все основное освещение"

    payload: 0 // DPT 1.001 (0 = выключить)

    };

    // 2. Команда включения ДЕЖУРНОГО света (DALI)

    const nightLightOn = {

    topic: "dali/cmd",

    payload: {

    "address": "g1", // Группа DALI "Дежурный свет"

    "command": "dapc", // Direct Arc Power Control

    "value": 25, // Установить яркость ~10% (нелинейная шкала DALI 0-254)

    "transition_time": 5 // Плавное включение/переход

    }

    };

    // 3. Активация охраны периметра (MQTT)

    const securityPerimeterArm = {

    topic: "home/security/set_state",

    payload: {

    "state": "armed_perimeter",

    "source": "scene_night"

    }

    };

    // Возвращаем вложенный массив для отправки 3-х сообщений в Выход 1

    return [ [mainLightOff, nightLightOn, securityPerimeterArm] ];

    Как видите, паттерн остается тем же. Меняется только содержимое `function`-узла, описывающего логику конкретного сценария. Это делает систему чрезвычайно гибкой и легкой для расширения новыми режимами («Гости», «Киносеанс» и т.д.).

    Чек-лист проверки режима «Night»

    Перед развертыванием сценария в рабочий контур, пройдитесь по тест-плану:

    Заключение: Обработка исключений и ручных корректировок

    Созданные нами базовая архитектура и сценарии смены режимов будут работать идеально... ровно до первого вмешательства человека. Представьте ситуацию: активирован режим «Ночь` (Night)`. По сценарию свет должен включаться максимум на 15%. Но пользователю экстренно потребовалось найти вещь, и он вручную через физический выключатель или приложение выставил яркость на 100%.

    Текущая базовая логика никак не запомнит это, и при следующем срабатывании датчика движения снова сбросит яркость до 15%. Это не только раздражает, но и нарушает фундаментальные правила UX (пользовательского опыта) умного дома.

    Конфликт приоритетов: автоматика против ручного управления

    Возникает фундаментальный вопрос: какой приоритет выше — у автоматического сценария по датчикам или у прямой ручной команды? Правильный ответ — приоритет всегда у ручной команды, но временно.

    Для решения этой проблемы в автоматизацию вводится концепция контекстного флага `manual_override` (ручное переопределение).

    Как реализовать логику переопределения:
  • Фиксация ручного действия. Отдельный узел (например, `events: state` в Home Assistant или `Device Event` в Sprut.hub) отслеживает изменение состояния светильника. Если смена вызвана физическим нажатием (во многих системах это видно по отсутствию `context.user_id` или свойству `trigger_source`), запускается цепочка перехвата.
  • Установка флага. Поток устанавливает переменную в контексте узла или потока:
  •    // При ручном вмешательстве устанавливаем флаг

    flow.set("light_override_bedroom", true);

  • Проверка в основном сценарии. Глобальный сценарий освещения перед отправкой команды _обязательно_ проверяет этот флаг:
  •    // В узле Function перед вызовом Call Service

    if (flow.get("light_override_bedroom") === true) {

    return null; // Прервать выполнение, ручная команда важнее

    }

    // ...иначе продолжаем автоматику

  • Тайм-аут (Сброс). Флаг `override` нельзя оставлять навсегда. Добавьте узел `Delay` или `Trigger` (например, на 2 часа), который по истечении времени выполнит `flow.set("light_override_bedroom", false)`. Также флаги должны принудительно сбрасываться при глобальной смене режима (например, при утреннем переходе из `Night` в `Home`).
  • Защита от сбоев: Сохранение состояний (Persistence)

    Что случится, если контроллер или сервер Node-RED жестко перезагрузится из-за перебоев с питанием в середине ночи? Без механизма персистентности (постоянного хранения) система "забудет", что был активен режим `Night`. Глобальные переменные в оперативной памяти сотрутся, и системы вернутся в состояние по умолчанию, потенциально разбудив дом ярким светом.

    Именно поэтому критически важно настроить сохранение контекста на энергонезависимый накопитель. Если вы используете Node-RED, вам потребуется внести правки в конфигурационный файл `settings.js`:

    // Найдите раздел contextStorage в settings.js и приведите к виду:
    

    contextStorage: {

    default: "memory", // Для обычных быстрых переменных ОЗУ

    memory: { module: 'memory' },

    file: { module: 'localfilesystem' } // Для хранения на диске

    }

    > 💡 Как использовать: После правильной настройки сохраняйте критически важные переменные режимов во внешнее хранилище, указывая третий аргумент `"file"`.

    > `global.set("home_mode", "Night", "file");`

    > При перезапуске системы контроллер прочитает `global.get("home_mode", "file")`, увидит значение `Night` и штатно инициализирует маршрутизацию без сброса статуса.

    Чек-лист проверки надёжности логики

    Прежде чем переносить реализованные режимы в "боевую" эксплуатацию для постоянного проживания, прогоните дом по этому тест-плану:

    Что дальше

    На базе созданного архитектурного паттерна (`Link In -> RBE/Filter -> Switch -> Function -> Call Service`) можно собирать любые композитные сценарии, которые объединяют группы устройств.

    Например, следующие режимы-надстройки легко ложатся в нашу топологию:

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