ГлавнаяАкадемияСценарии умного дома: режимы, состояния, приоритеты → Практика: Реализация ручного override для группы света

Практика: Реализация ручного override для группы света

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

Введение: Проблема ручного управления в автоматизированных системах

Автоматизация, особенно в контексте умного дома, призвана сделать жизнь комфортнее, предвосхищая потребности жильцов. Сценарии, основанные на датчиках движения, времени суток или других триггерах, эффективно справляются с рутинными задачами, такими как управление освещением. Однако существует фундаментальный конфликт: самая интеллектуальная автоматика не способна на 100% угадать намерения человека в каждый конкретный момент времени.

Представьте ситуацию: вы уютно устроились с книгой в кресле. Автоматика, не зафиксировав движения в течение 5 минут, решает, что в комнате никого нет, и гасит свет. Вам приходится махать рукой, чтобы "оживить" датчик. Это не комфорт, а раздражение. Именно для решения таких проблем и был разработан паттерн "Ручной Override" (Ручное Управление/Переопределение).

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

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

В рамках этой практической работы мы создадим комплексный поток в Node-RED, который решает эту задачу. Мы будем использовать следующие ключевые узлы:

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

---

Практика: Создание базового сценария освещения

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

Цель: Свет в гостиной должен включаться при обнаружении движения и выключаться, если движение не фиксируется в течение 5 минут.

Шаг 1: Создание потока и настройка входных данных

  • Создайте новую вкладку в редакторе Node-RED и назовите ее "Освещение: Гостиная".
  • Разместите на поле узел `mqtt in`. Он будет принимать сообщения от PIR-сенсора.
  • * Сервер (Broker): Выберите ваш настроенный MQTT-брокер.

    * Топик: `hi/devices/pir_living_room/motion`

    * Выход (Output): `разобранный JSON объект`

    * Имя: `Движение в гостиной`

    Мы предполагаем, что датчик отправляет сообщения в формате JSON, соответствующем нашему контракту сообщений:

    {
    

    "motion": true,

    "battery": 87,

    "source": "pir_living_room_01",

    "ts": 1678886400000

    }

    Шаг 2: Фильтрация и запуск таймера

  • Соедините выход узла `mqtt in` с входом узла `switch`. Нам нужно реагировать только на `motion: true`.
  • * Свойство: `msg.payload.motion`

    * Правило: `является true`

  • Соедините выход узла `switch` с входом узла `trigger`. Этот узел является сердцем нашего таймера автовыключения.
  • * Отправить: (первое сообщение) — JSON `{ "state": "ON" }`

    * затем, после: `5 минут`

    * отправить: (второе сообщение) — JSON `{ "state": "OFF" }`

    * Действие при получении нового сообщения: `продлить таймер`

    * Имя: `Таймер на 5 минут`

    Эта конфигурация работает следующим образом:

    Шаг 3: Формирование и отправка команды

  • Разместите узел `function` и соедините с ним выход узла `trigger`. Этот узел будет формировать финальное сообщение для реле.
  • * Имя: `Формат для реле`

    * Код:

        // Входящее msg.payload: { "state": "ON" } или { "state": "OFF" }

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

    msg.topic = 'hi/devices/light_group_living_room/set';

    // Payload уже в правильном формате, но мы можем добавить

    // метаданные для аудита.

    msg.audit = {

    trigger: "auto_motion",

    source: "pir_living_room_01",

    requested_state: msg.payload.state

    };

    // Визуальный статус для отладки

    if (msg.payload.state === "ON") {

    node.status({ fill: "green", shape: "dot", text: "Включить по движению" });

    } else {

    node.status({ fill: "red", shape: "dot", text: "Выключить по таймеру" });

    }

    return msg;

  • Соедините выход узла `function` с узлом `mqtt out`.
  • * Сервер (Broker): Выберите ваш MQTT-брокер.

    * Топик: Оставьте пустым, так как он устанавливается в узле `function`.

    * Имя: `Управление светом гостиной`

    ASCII-схема базового потока:
    [mqtt in]─────────[switch]─────────[trigger]─────────[function]─────────[mqtt out]
    

    (Движение) (msg.payload.motion) (Таймер 5 мин) (Формат для реле) (Управление светом)

    Теперь у вас есть рабочий сценарий автоматического управления светом. Протестируйте его: сымитируйте отправку MQTT-сообщения о движении и убедитесь, что свет включается, а через 5 минут (или установленное вами время) — выключается.

    ---

    Реализация логики Override с помощью контекста Flow

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

    > 💡 Подсказка: Используйте `flow` контекст вместо `global`, чтобы изолировать логику override в рамках одного сценария. Это предотвратит случайные конфликты между разными автоматизациями и сделает ваш проект более модульным — это ключевой принцип для создания надежных и масштабируемых решений, таких как переиспользуемые компоненты (subflows).

    Шаг 1: Добавление ручного управления

  • Добавьте на поле еще один узел `mqtt in`. Он будет принимать команды от настенного выключателя.
  • * Топик: `hi/devices/wall_switch_living_room/action`

    * Имя: `Настенный выключатель`

    Предположим, выключатель при нажатии отправляет сообщение с `msg.payload`, равным `{"action": "toggle"}`.

    Шаг 2: Создание флага Override

  • Соедините выход узла "Настенный выключатель" с новым узлом `function`. Здесь будет происходить магия: мы будем не только управлять светом, но и устанавливать флаг блокировки.
  • * Имя: `Ручное управление + Override`

    * Код:

        // Получаем текущее состояние флага override из контекста потока

    let isOverrideActive = flow.get('manualOverride') || false;

    // Получаем последнее известное состояние света (если оно есть)

    let lastLightState = flow.get('lightState') || "OFF";

    // 1. Активируем режим ручного управления

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

    flow.set('manualOverride', true);

    // 2. Определяем новое состояние света (логика "toggle")

    let newLightState = (lastLightState === "ON") ? "OFF" : "ON";

    flow.set('lightState', newLightState); // Сохраняем новое состояние

    // 3. Формируем команду для реле

    msg.payload = { "state": newLightState };

    msg.topic = 'hi/devices/light_group_living_room/set';

    // 4. Добавляем данные для аудита

    msg.audit = {

    trigger: "manual_switch",

    source: "wall_switch_living_room_01",

    requested_state: newLightState

    };

    // 5. Устанавливаем визуальный статус

    node.status({ fill: "blue", shape: "ring", text: `Override: ${newLightState} в ${new Date().toLocaleTimeString()}` });

    return msg;

  • Соедините выход этого `function` узла с тем же узлом `mqtt out`, который управляет светом. Таким образом, и автоматика, и ручное управление используют одну и ту же точку вывода.
  • Шаг 3: Блокировка автоматики

    Теперь самое главное: нужно заставить автоматический сценарий "уважать" флаг `manualOverride`.

  • В базовом потоке, созданном ранее, вставьте узел `switch` между узлом `mqtt in` (Движение в гостиной) и узлом `trigger` (Таймер на 5 минут).
  • * Имя: `Проверка Override`

    * Свойство: `flow.manualOverride` (выберите тип `flow`)

    * Правило 1: `is false` или `is undefined`. Соедините этот выход (`1`) с узлом `trigger`.

    * Правило 2: `иначе` (`is true`). Этот выход (`2`) никуда не подключайте. Он будет отбрасывать сообщения.

    Теперь, когда `flow.manualOverride` установлен в `true`, сообщения от датчика движения не смогут запустить или продлить таймер `trigger`, и автоматика будет эффективно заблокирована.

    Обновленная ASCII-схема:
    // Автоматический поток
    

    [mqtt in]────[Проверка Override]──┬──[trigger]──[function]──┬──[mqtt out]

    (Движение) (switch) | (Таймер) (Формат) | (Свет)

    | |

    v отброс |

    (сообщения блокированы) |

    |

    // Ручной поток |

    [mqtt in]────────[function: "Ручное управление"]────────────+

    (Выключатель) (установка flow.manualOverride)

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

    ---

    Механизмы возврата в автоматический режим

    Заблокировать автоматику — это половина дела. Не менее важно обеспечить надежный и предсказуемый способ вернуться в автоматический режим. Существует два основных подхода: автоматический (по тайм-ауту) и ручной (по действию пользователя).

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

    1. Автоматический сброс по таймеру

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

  • Вернитесь к узлу `function` ("Ручное управление + Override"). Нам нужно, чтобы при установке флага `manualOverride` запускался долгосрочный таймер. Модифицируем узел, добавив ему второй выход.
  • * Имя: `Ручное управление + Запуск сброса`

    * Выходы: `2`

    * Код:

        // ... (код из предыдущего шага) ...

    // 3. Формируем команду для реле

    msg.payload = { "state": newLightState };

    msg.topic = 'hi/devices/light_group_living_room/set';

    // ... (код аудита и статуса) ...

    // Создаем второе сообщение для запуска таймера сброса

    let resetTimerMsg = { payload: "start_reset_timer" };

    // Отправляем команду на свет на первый выход, а команду на запуск таймера - на второй

    return [msg, resetTimerMsg];

  • Добавьте узел `trigger` на поле.
  • * Имя: `Таймер сброса Override (2ч)`

    * Отправить: `ничего` (не отправлять сразу)

    * затем, после: `2 часов`

    * отправить: `{ "action": "reset_override" }`

    * Действие при получении: `запустить/перезапустить таймер`

  • Соедините второй выход `function` узла "Ручное управление + Запуск сброса" с входом этого нового узла `trigger`.
  • Добавьте еще один узел `function` для обработки сброса.
  • * Имя: `Сброс флага Override`

    * Код:

        // Сбрасываем флаг, возвращая систему в автоматический режим

    flow.set('manualOverride', false);

    node.status({ fill: "green", shape: "dot", text: `Авто-сброс в ${new Date().toLocaleTimeString()}` });

    // Важно! После сброса нужно синхронизировать состояние света.

    // Если в комнате есть движение, свет должен остаться включенным.

    // Эту логику мы рассмотрим в разделе "Синхронизация состояния".

    // Пока что просто сбрасываем флаг.

    // Останавливаем поток, чтобы это сообщение не пошло на реле

    return null;

  • Соедините выход таймера сброса с этим узлом.
  • 2. Ручной сброс

    Пользователь должен иметь возможность сам вернуть автоматику, не дожидаясь таймера. Для этого можно использовать, например, двойное нажатие клавиши.

  • Предположим, ваш выключатель при двойном нажатии отправляет в топик `hi/devices/wall_switch_living_room/action` сообщение `{"action": "double_click"}`.
  • Модифицируем узел `mqtt in` "Настенный выключатель" так, чтобы он слушал и этот топик (или используем `switch` для маршрутизации сообщений по `msg.payload.action`).
  • Соедините путь для `double_click` с тем же узлом `function` ("Сброс флага Override"), который мы создали для автоматического сброса.
  • 3. Синхронизация состояния

    Когда `override` сбрасывается, может возникнуть неприятная ситуация: в комнате есть люди, а свет гаснет, потому что автоматика "не в курсе". Решение — при сбросе флага проверить текущее состояние датчика движения.

  • Нам нужно где-то хранить последнее состояние датчика. Модифицируем поток автоматики: добавим небольшой `function` узел после `mqtt in` (Движение), который будет сохранять состояние движения:
  • * Имя: `Сохранить состояние движения`

    * Код: `flow.set('lastMotionState', msg.payload.motion); return msg;`

  • Теперь улучшим узел "Сброс флага Override":
  • * Код:

        flow.set('manualOverride', false);

    node.status({ fill: "green", shape: "dot", text: `Возврат в Авто-режим в ${new Date().toLocaleTimeString()}` });

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

    // Если после сброса в комнате есть движение,

    // нужно заново запустить логику автоматического включения.

    if (lastMotionState === true) {

    // Формируем сообщение, идентичное сообщению от датчика движения,

    // чтобы "подтолкнуть" автоматическую ветку.

    return { payload: { motion: true } };

    }

    // Если движения нет, ничего не делаем и останавливаем поток

    return null;

  • Соедините выход этого узла с узлом `trigger` (Таймер на 5 минут). Таким образом, при сбросе override и наличии движения мы принудительно запускаем сценарий авто-включения, и свет не погаснет.
  • ---

    Итоги: Тестирование и обобщение паттерна

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

    Финальная ASCII-схема потока:
    //----------- ВЕТКА АВТОМАТИЗАЦИИ -----------
    

    [mqtt in]──[func: Сохр. сост.]──[switch: Проверка Override]─┬─[trigger: Таймер 5м]──[func: Формат]─┬──[mqtt out]

    (Движение) (flow.lastMotionState) (flow.manualOverride) | | (Свет)

    | |

    └─────(Если движения нет)────────────────>┘

    //----------- ВЕТКА РУЧНОГО УПРАВЛЕНИЯ -----------

    [mqtt in]──────────[switch: Тип нажатия]──┬─(toggle)──[func: Уст. Override]─┬──(на 1-й вых.)─────>┘

    (Выключатель) (msg.payload.action) | (2 вых.) |

    | └─(на 2-й вых.)─[trigger: Таймер 2ч]─┬─[func: Сброс Override]─┬─(если есть движение)─> (в trigger: Таймер 5м)

    └─(double_click)──────────────────────────────────────────┘ └─(если нет движения)─> (null)

    План тестирования

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

  • Автоматический режим:
  • * Зайдите в комнату. Ожидаемый результат: Свет включается.

    * Покиньте комнату. Ожидаемый результат: Свет выключается через 5 минут.

  • Активация ручного режима:
  • * Войдите в комнату, свет включился. Нажмите настенный выключатель. Ожидаемый результат: Свет погас.

    * Продолжайте двигаться. Ожидаемый результат: Свет НЕ включается. Режим `override` активен.

  • Автоматический сброс:
  • * Активируйте ручной режим. Ничего не делайте. Ожидаемый результат: Через 2 часа (или тестовое время) система возвращается в автоматический режим. Если в комнате есть движение, свет включится.

  • Ручной сброс:
  • * Активируйте ручной режим. Совершите действие ручного сброса (например, двойное нажатие). Ожидаемый результат: Система немедленно возвращается в автоматический режим. Светодиод на выключателе (если есть) меняет цвет.

    Обобщение паттерна "Override"

    Созданный нами механизм является универсальным. Его можно и нужно применять для других подсистем:

    Этот урок стал практическим введением в один из важнейших принципов проектирования умного дома — иерархию приоритетов сценариев. Наш поток является первым практическим воплощением этого правила, устанавливая простой, но нерушимый принцип: Ручное управление > Автоматизация. В дальнейшем мы будем надстраивать эту иерархию, добавляя уровни для режимов, расписаний и сценариев безопасности.

    Что дальше

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