ГлавнаяАкадемияСценарии умного дома: режимы, состояния, приоритеты → Режимы vs. Сцены: определения, примеры, иерархия

Режимы vs. Сцены: определения, примеры, иерархия

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

Введение: Определения Сцен и Режимов

ведение: Определения Сцен и Режимов

Фундаментом для построения любой гибкой и масштабируемой системы умного дома является четкое архитектурное разделение понятий Сцена (Scene) и Режим (Mode). Смешивание этих двух концепций — одна из самых распространенных ошибок, ведущая к созданию запутанных, трудноподдерживаемых и нестабильных потоков автоматизации в среде Node-RED. В рамках методологии нашей академии мы придерживаемся строгих определений, которые позволяют избежать этой путаницы.

> ℹ️ Информация: В разных экосистемах (Apple HomeKit, Google Home, Amazon Alexa) терминология может отличаться, что часто вносит сумятицу. Например, то, что в одной системе называется "сценой", в другой может быть "рутиной" или "автоматизацией". В рамках платформы HI и стека Node-RED мы придерживаемся строгих определений: Сцена — это действие, Режим — это состояние. Это разделение является не просто семантическим, а архитектурным требованием.

Давайте разберем каждое понятие в деталях, чтобы заложить прочный фундамент для будущих автоматизаций.

Что такое Сцена?

Сцена — это однократное, атомарное действие или последовательность действий, инициируемое определенным событием для изменения состояния группы устройств. Сцена — это "глагол" вашей системы автоматизации. Она отвечает на вопрос "Что сделать?". Кратковременность: Сцена выполняется и завершается. Она не имеет собственного длительного состояния. Система не "находится в сцене 'Кино'", она выполнила* сцену 'Кино'. Практические примеры Сцен:

Что такое Режим?

В отличие от Сцены, Режим, как мы установили ранее в курсе, — это не действие, а долговременное состояние системы. Режим — это "прилагательное" вашей системы, отвечающее на вопрос "В каком состоянии находится система?".

Ключевое отличие: Режим не выполняет действий сам по себе, а служит контекстом или условием для выполнения (или блокировки) Сцен. Например, сам по себе режим 'Ночь' ничего не делает, но его наличие заставляет датчик движения запускать сцену 'Ночная подсветка', а не 'Яркий свет'. Система выполняет сцену 'Кино', но находится в режиме 'Ночь'.

Практические примеры Режимов (для сравнения со Сценами):

Практикум: Сцена или Режим?

Чтобы убедиться, что разница ясна на 100%, выполните небольшое упражнение. Классифицируйте следующие элементы системы умного дома, отвечая на вопросы "Что сделать?" или "Какое состояние?".

Попробуйте сначала дать ответ самостоятельно, а затем сверьтесь с ключом.
  • "Пробуждение" (включить теплый свет плавно, открыть шторы, запустить кофеварку).
  • "Тихий час" (период, когда музыка играет тише, а уведомления отключены).
  • "Вечеринка" (включить цветную подсветку, музыку на максимум).
  • "Охрана периметра" (система игнорирует окна днем, но следит за дверьми всегда).
  • > 💡 Результат (проверьте себя):

    > 1. Это Сцена. Присутствуют конкретные действия по изменению состояния физических устройств (включить, открыть).

    > 2. Это Режим. Это контекст (состояние), в котором система меняет правила своей работы (глушит уведомления, меняет громкость).

    > 3. Это Сцена. Это однократный триггер для запуска сценария освещения и музыки.

    > 4. Это Режим. Это длительное состояние безопасности, влияющее на логику работы датчиков.

    Итог: Золотое правило разделения

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

    Золотое архитектурное правило:

    > Разделяйте логику на два уровня. Уровень состояний (Режимы) управляет вторым уровнем (Сценами). Режимы формируют контекст, а Сцены выполняют работу с учетом этого контекста.

    Понимание этой четкой границы (Действие vs. Состояние) — первый шаг к проектированию надежных автоматизаций. Далее мы рассмотрим, как эта концепция транслируется непосредственно в логику управления умным домом.

    Реализация Сцен: от простого к сложному

    еализация Сцен: от простого к сложному

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

    > ⚠️ Важно (UX и Overrides): Принцип работы сцены — Fire-and-Forget (выстрелил и забыл). Сцена применяет состояние единоразово в момент вызова. Если после запуска сцены «Кино» пользователь подойдет к физическому выключателю и включит свет (сработает ручной override), система не будет пытаться выключить его обратно. Сцена не удерживает состояние, в отличие от Режимов, логику которых мы разберем позже.

    Простейший запуск: узел `Inject`

    Для первоначальной отладки структурного ядра сцен идеально подходит узел `Inject`. Он позволяет простым нажатием кнопки в web-интерфейсе Node-RED запустить весь поток.

    В дальнейшем этот узел-заглушку легко заменить или дополнить реальными триггерами:

    Пример сложной сцены: «Вечерний кинопросмотр»

    Задача: Создать сцену, которая по одной команде выполняет следующие действия:
  • Выключает основной свет в гостиной (управляется по шине DALI).
  • Включает контурную LED-подсветку на синий цвет и яркость 30% (управляется через MQTT).
  • Опускает моторизованные шторы (управляются через модули расширения по шине RS-485 Modbus).
  • Включает питание экрана аудиосистемы/проектора (управляется через реле, подключенные к GPIO-выходам контроллера).
  • Архитектура потока в Node-RED:
                 +-----------+
    

    | Inject | (Запуск сцены 'Кино')

    | "Кино" |

    +-----------+

    |

    v

    +------------------+-------------------+

    | Function: "Формирователь команд" | <-- В настройках узла нужно

    | (Создает 4 отдельных сообщения) | указать Outputs: 4

    +------------------+-------------------+

    | | | |

    (Вых.1) (Вых.2) (Вых.3) (Вых.4)

    | | | |

    v v v v

    +--------+ +--------+ +--------+ +--------+

    | DALI | | MQTT | | Modbus | | GPIO |

    | Out | | Out | | Write | | Out |

    | (Свет) | | (Лента)| | (Шторы)| | (Реле) |

    +--------+ +--------+ +--------+ +--------+

    Центральным элементом этой архитектуры является узел `Function` "Формирователь команд". Он принимает один сигнал и генерирует на своих выходах несколько независимых объектов `msg`, каждый из которых настроен под контракт(payload) своего исполнителя.

    Код узла `Function` (с 4 выходами):
    // Входящий msg игнорируется, узел сам формирует веер команд
    
    

    // Выход 1: Команда для DALI (выключить группу света 1)

    // Предполагается, что узел DALI ожидает msg.payload в виде строки

    let msg1 = {

    // Контракт сообщения для dali out (зависит от используемой библиотеки DALI)

    payload: "group 1 level 0"

    };

    // Выход 2: Команда для MQTT (управление LED-лентой через Zigbee2MQTT или подобный сервис)

    let msg2 = {

    topic: "hi/living_room/led_strip/set",

    payload: {

    "state": "ON",

    "brightness": 77, // ~30% от максимума 255

    "color": { "r": 0, "g": 0, "b": 255 }

    },

    qos: 1,

    retain: false

    };

    // Выход 3: Команда для Modbus (опустить моторизованные шторы)

    // Модуль (SlaveID=5) ожидает значение 1 в Holding Register 100

    let msg3 = {

    // Контракт сообщения для пакета node-red-contrib-modbus

    payload: {

    'value': 1,

    'fc': 6, // FC 6: Preset Single Register

    'unitid': 5,

    'address': 100,

    'quantity': 1

    }

    };

    // Выход 4: Команда для реле контроллера (включить питание проектора)

    // Ожидается цифровое значение 1 для включения (GPIO/Relay)

    let msg4 = {

    // Контракт сообщения выходного узла rpi-gpio или io-интерфейса

    payload: 1

    };

    // Возвращаем массив из 4 элементов.

    // Каждый элемент уйдет на свой физический выход (порт) узла Function.

    return [msg1, msg2, msg3, msg4];

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

    Чек-лист сборки и отладки сцены

    Прежде чем отправлять команды на реальные устройства, рекомендуется всегда тестировать сцены "в холостую" (Dry Run).

    🛠 Практическое мини-задание: Сцена «Уход из дома»

    Попробуйте закрепить принцип веерной рассылки, создав простейшую сцену полного отключения оборудования.

    Задача: Собрать флоу, который симулирует нажатие кнопки у двери «Выключить всё».
  • Создайте узел `Inject` с именем «Ушли».
  • Добавьте узел `Function` и настройте его на 3 выхода.
  • Внутри функции напишите код, который возвращает три сообщения:
  • * Выход 1: Команда на виртуальный общий выключатель освещения (MQTT топик `home/all_lights/set`, payload: `{"state": "OFF"}`).

    * Выход 2: Команда на закрытие рольставней (payload должен быть числом `0`).

    * Выход 3: Команда на отключение розеточной группы (payload должен быть boolean `false`).

  • Подключите три узла `Debug` к выходам `Function`.
  • Сделайте `Deploy` и нажмите кнопку Inject.
  • Ожидаемый результат (Тест-план):

    В панели `debug` справа должны синхронно (в одну секунду) появиться 3 сообщения:

  • `msg.payload: object` со структурой `{state: "OFF"}`
  • `msg.payload: number` со значением `0`
  • `msg.payload: boolean` со значением `false`
  • Если вы видите сообщение «undefined» или отправляется только одно сообщение, убедитесь, что оператор `return` возвращает именно массив: `return [msg1, msg2, msg3];`, а количество выходов узла настроено на 3.

    Управление Режимами через глобальный контекст

    правление Режимами через глобальный контекст

    Если сцены — это кратковременные действия, то режимы — это долговременные состояния. Идеальным местом для хранения таких состояний в Node-RED является глобальный контекст (global context). Это область памяти, доступная из любого потока на любой вкладке в рамках одного экземпляра Node-RED.

    > 💡 Подсказка: Используйте единый стандарт именования для переменных режимов, например, `g_mode_security` (охрана), `g_mode_presence` (присутствие), `g_mode_daylight` (день/ночь). Префикс `g_` (global) или суффикс `_mode` сразу дает понять, что это за переменная и где она хранится. Это упрощает отладку и чтение потоков другими инженерами.

    Запись и чтение режимов

    Для программной работы с глобальным контекстом используются две простые функции, доступные внутри узла `Function` (JavaScript):

    > 💡 Новичкам на заметку: Помимо узла `Function`, задавать и читать режимы можно без единой строчки кода, используя стандартные узлы `Change` или `Switch`. В них достаточно выбрать поле `global.` и ввести имя вашей переменной (например, `global.g_mode_presence`).

    Обеспечение сохранности режимов (Persistence)

    Критически важным свойством режимов является их сохранность (persistence). Режим "Отпуск" или "Никого" не должен сбрасываться в состояние по умолчанию после перезагрузки контроллера HI (например, из-за сбоя питания или перезапуска службы). По умолчанию Node-RED хранит контекст в оперативной памяти (сбрасывается при рестарте), поэтому нам нужно включить его сохранение в файловую систему.

    Для этого в файле настроек Node-RED (`settings.js`) необходимо добавить или изменить конфигурацию хранилища контекста `contextStorage`:

    // Файл settings.js на контроллере HI
    

    ...

    contextStorage: {

    default: {

    module: "localfilesystem"

    },

    // Опционально: можно добавить хранилище только в ОЗУ для частых/некритичных данных

    memoryOnly: {

    module: "memory"

    }

    },

    ...

    После внесения этого изменения и перезапуска Node-RED все данные, записываемые в контекст, будут автоматически сохраняться в файлах в папке `/userdir/context/`. Это гарантирует 100% восстановление системных режимов после любых перезагрузок контроллера.

    Учебный пример: Управление режимом "Присутствие"

    Задача: Создать архитектуру потока, который переключает режим присутствия `g_mode_presence` между значениями `away` (Никого) и `home` (Дома) на основе входящего MQTT-сообщения от панели охранной системы. Также требуется логика для ручной проверки текущего статуса. Архитектура потока:
    +-----------------------+      +-------------------+      +-------------------------+
    

    | MQTT In: | | Function: | | Debug: "Режим изменен" |

    | home/security/state |----->| "Установить режим"|----->| |

    +-----------------------+ +-------------------+ +-------------------------+

    +----------------+ +-------------------+ +-------------------------+

    | Inject: | | Function: | | Debug: "Текущий статус" |

    | "Проверить" |------>| "Прочитать режим" |----->| |

    +----------------+ +-------------------+ +-------------------------+

    Шаг 1: Код узла `Function` "Установить режим"

    Этот узел анализирует входящие данные, меняет статус (только если он реально изменился) и формирует красивое уведомление в интерфейсе.

    // Ожидаемый msg.payload от охранной системы: "ARMED_AWAY" (поставлено на охрану) или "DISARMED" (снято)
    
    

    // 1. Получаем текущее значение режима (или "unknown", если переменная еще пуста)

    let currentMode = global.get("g_mode_presence") || "unknown";

    let newMode;

    // 2. Транслируем статус охранной системы в наш глобальный режим умного дома

    switch (msg.payload) {

    case "ARMED_AWAY":

    newMode = "away";

    break;

    case "DISARMED":

    newMode = "home";

    break;

    default:

    // Если пришло неизвестное состояние (например, сервисный режим), ничего не меняем

    node.warn("Неизвестный статус охранной системы: " + msg.payload);

    return null;

    }

    // 3. Защита от дублей: обновляем режим только если он фактически изменился

    if (newMode !== currentMode) {

    global.set("g_mode_presence", newMode);

    // Визуализируем статус прямо под узлом Function

    node.status({ fill: "green", shape: "dot", text: "Режим: " + newMode });

    // Формируем payload с полной историей изменения

    msg.payload = {

    event: "mode_change",

    mode: "presence",

    old_value: currentMode,

    new_value: newMode,

    timestamp: Date.now()

    };

    // Передаем сообщение дальше (например, в логгер или триггер других сцен)

    return msg;

    }

    // Если режим пришел тот же самый (например, пришел дубль MQTT сообщения), останавливаем поток

    return null;

    Шаг 2: Код узла `Function` "Прочитать режим"

    Чтобы любая сцена (включение света, управление отоплением) могла узнать, есть ли кто-то дома, нам нужен блок чтения.

    // Читаем текущий режим. Если он еще не задан, считаем, что мы дома (безопасный дефолт)
    

    let currentPresence = global.get("g_mode_presence") || "home";

    msg.payload = "Текущий статус присутствия: " + currentPresence;

    return msg;

    Практическое мини-задание: Валидация потока

    Для закрепления работы с логикой и глобальным контекстом, соберите вышеупомянутый поток в своем рабочем пространстве Node-RED и выполните следующий тест-план:

  • Создайте симуляторы событий: Добавьте два узла `Inject`. Настройте первый на отправку `msg.payload` типа string со значением `ARMED_AWAY`. Второй — со значением `DISARMED`. Подключите их ко входу узла "Установить режим".
  • Проверьте первичное переключение: Нажмите кнопку на узле с `ARMED_AWAY`.
  • Ожидаемый результат:* Появится статусная точка под узлом `Режим: away`. В панели Debug отобразится JSON-объект изменения режима.

  • Проверьте защиту от дублирования: Нажмите на тот же узел `ARMED_AWAY` повторно.
  • Ожидаемый результат:* В панели Debug не должно появиться ничего нового. Поток корректно остановился командой `return null`, предотвратив выполнение цепочки "паразитных" сценариев.

  • Считайте режим: Нажмите на Inject "Проверить" перед нижним узлом функции.
  • Ожидаемый результат:* Узел "Прочитать режим" вернет актуальную строку `Текущий статус присутствия: away`.

    Этот подход создает надежную, централизованную и изолированную точку управления каждым режимом системы. Любой другой скрипт в вашем умном доме теперь может в реальном времени проверять глобальное состояние `g_mode_presence`, прежде чем выполнять ресурсоемкие или тревожные действия.

    Иерархия и взаимодействие: Режим как условие для Сцены

    ерархия и взаимодействие: Режим как условие для Сцены

    Теперь, когда мы определили и реализовали Сцены (действия) и Режимы (состояния) как отдельные сущности, мы можем построить между ними правильную иерархическую связь. Этот шаг критически важен для перехода от единичных автоматизаций к по-настоящему "умной" и саморегулируемой системе.

    Основной принцип иерархии: Режимы являются «фильтрами» или «предусловиями» для выполнения Сцен и автоматизаций.

    Это означает, что перед запуском любой логики, связанной со сценой, система должна сначала проверить текущий режим и на его основании принять решение — выполнять сцену, модифицировать ее или полностью заблокировать.

    > ⚠️ Внимание: Избегайте циклических зависимостей, когда сцена пытается изменить режим, который, в свою очередь, является условием для запуска этой же сцены. Это может привести к бесконечным циклам или непредсказуемому поведению системы. Пример: Сцена "Ночной дозор" включается в режиме "Ночь", а в конце своего выполнения пытается установить режим "Ночь". Это классический рецепт нестабильности. Режимы должны изменяться отдельными, независимыми потоками.

    Базовая реализация иерархии с помощью узла `Switch`

    Узел `switch` в Node-RED — идеальный инструмент для реализации блокирующей или маршрутизирующей логики. Он позволяет направить поток по разным путям в зависимости от значения переменной режима.

    Пример: Сцена «Утренний свет» и режим «Отпуск» Задача: Сцена «Утренний свет» должна плавно включать освещение в спальне в 7:00 утра, но только если в доме кто-то есть (режим «Отпуск» выключен). Архитектура потока:
    +----------+      +-------------------+      +----------------------------+
    

    | Inject |----->| Function: |----->| Switch: "Проверка режима" |

    | (7:00 AM)| | "Получить режим" | +----------------------------+

    +----------+ +-------------------+ | (msg.vacation_mode != true)

    |

    v

    +----------------+

    | Flow: "Сцена |

    | 'Утренний свет'|

    +----------------+

  • Узел `Inject` настраивается на запуск каждый день в 7:00.
  • Узел `Function` "Получить режим" считывает значение глобального режима и помещает его в `msg` для дальнейшей обработки.
  •     // Получаем текущее значение режима "Отпуск"

    const vacationMode = global.get("g_mode_vacation") || false;

    // Помещаем его в объект msg, чтобы узел switch мог его использовать

    msg.vacation_mode = vacationMode;

    return msg;

  • Узел `Switch` "Проверка режима" настраивается для проверки свойства `msg.vacation_mode`.
  • * Правило 1: `is false` (или `is not equal to true`).

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

    Этот подход превращает режим в мощный "вентиль", который может перекрывать или открывать путь для десятков различных автоматизаций.

    Многоуровневые проверки: Вложенность и приоритеты режимов

    Базовую архитектуру можно усложнить, обеспечив взаимодействие нескольких режимов (глобальных и локальных). Например, глобальный режим `g_mode_vacation: true` имеет высший приоритет и блокирует практически все автоматизации. В то же время, локальный режим контекста комнаты `flow.bedroom_mode: 'sleep'` блокирует только сценарии, связанные со спальней (например, включение верхнего яркого света по датчику движения), но не влияет на работу базовой инфраструктуры.

    Логика проверки в этом случае строится как каскад фильтров (выполняется последовательно):

  • Проверка 1: Глобальный режим «Отпуск». Если `true` — полное завершение потока.
  • Проверка 2: Глобальный режим «Присутствие». Если `away` — завершение потока.
  • Проверка 3: Локальный режим «Сон». Если `true` — направить поток на альтернативную ветку с модифицированной "тихой" сценой.
  • Если все блокирующие проверки пройдены со значением `false` — запустить стандартную сцену.
  • Такая многоуровневая фильтрация позволяет создавать чрезвычайно гибкие и предсказуемые системы, которые адекватно реагируют на сложный жизненный контекст пользователя.

    ---

    Практическое мини-задание: Настройка каскадной проверки

    Цель: Создать логику, которая реагирует на движение в гостиной, учитывая два режима разного уровня: глобальный (Отпуск) и локальный (Сон). Шаги реализации:
  • Разместите узел `Inject` (назовите его "Датчик движения").
  • Добавьте узел `Function` («Анализ состояний») для сбора режимов перед принятием решения:
  •    msg.is_vacation = global.get("g_mode_vacation") || false;

    msg.is_sleep = flow.get("f_mode_sleep") || false;

    return msg;

  • Подключите первый узел `Switch` («Проверка: Отпуск»):
  • Правило 1:* `msg.is_vacation` is `false`. (Подключите к следующему узлу).

    (Сообщения с `true` никуда не идут — сцена обрывается, так как дом пуст).*

  • К первому пути предыдущего `select` подключите второй узел `Switch` («Проверка: Сон»):
  • Правило 1:* `msg.is_sleep` is `false` → направить на узел `Change` (Установка яркости света на 100%).

    Правило 2:* `msg.is_sleep` is `true` → направить на узел `Change` (Установка ночной подсветки на 10%).

    Ожидаемый результат: При активации датчика система сначала убедитcя, что вы сейчас не в отпуске (иначе проигнорирует движение). Затем она переопределит результат (яркость) в зависимости от того, спит ли уже дом.

    Чек-лист проверки иерархии (Тест-план потока)

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

    > 💡 Итог раздела: Жесткое разделение потока на «блок проверки предусловий» и «блок исполнения» делает код чище. Если автоматизация неожиданно включается ночью, больше не нужно искать проблему глубоко в сценарии освещения — достаточно посмотреть значения глобальных режимов-вентилей перед узлом выполнения сцены.

    Заключение: Построение надежного сценарного слоя

    аключение: Построение надежного сценарного слоя

    В этом уроке мы заложили архитектурную основу для создания профессионального сценарного слоя на платформе HI. Мы четко разграничили два фундаментальных понятия:

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

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

    Чек-лист: Проверка архитектуры

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

    Практическое мини-задание

    > 💡 Задача: Перед вами пункты из технического задания от заказчика. Определите для каждого пункта, чем он должен быть реализован в архитектуре системы — Сценой или Режимом.

    >

    > 1. Включить полную подсветку участка при срабатывании периметральной охраны.

    > 2. Дом находится в состоянии "Отпуск".

    > 3. Родственники остались на ночь в гостевой спальне.

    > 4. Инициировать закрытие всех штор по нажатию кнопки «Ночь» на мастер-панели.

    Ожидаемый результат (нажмите для проверки) Включить полную подсветку участка* — Сцена (однократное мгновенное действие по триггеру тревоги). Состояние "Отпуск"* — Режим (долговременный статус энергосбережения и имитации присутствия). Родственники остались на ночь* — Режим (статус "Гости", который будет блокировать, например, сценарии автоматического поднятия штор в 7 утра в гостевой комнате). Инициировать закрытие всех штор...* — Сцена (прямая команда пользователя в моменте).

    Что дальше?

    Мы научились категорично разделять разовые действия и длительные состояния, выстраивая детерминированную иерархию системы. Но что произойдет, если два разных события попытаются запустить конфликтующие изменения одновременно? Например, сцена "Кино" плавно гасит свет, а параллельно срабатывает базовый датчик движения, который формирует сцену "Включить базовое освещение".

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