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

Практика: Реализация сценария управления шторами

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

Введение: Цели и задачи урока

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

  • Комфорт: Автоматическое закрытие штор во время просмотра кино, защита от слепящего утреннего солнца или создание приватности в вечернее время.
  • Энергосбережение: Пассивное управление температурой в помещении. Летом закрытые шторы на солнечной стороне помогают работе кондиционера, а зимой открытые — наоборот, позволяют солнечному теплу прогревать комнату, снижая нагрузку на систему отопления.
  • Безопасность: Имитация присутствия хозяев во время их длительного отсутствия. Периодическое открытие и закрытие штор создает впечатление, что дом не пустует.
  • Конечная цель данного урока — спроектировать и реализовать в среде Node-RED комплексный и отказоустойчивый поток (`flow`), который станет централизованным модулем управления приводами штор на объекте. Мы создадим не просто набор разрозненных команд, а полноценный «микросервис» внутри контроллера, который будет intelligently управлять состоянием штор, учитывая множество факторов.

    Для выполнения практических заданий нам понадобятся следующие компоненты:

    * Релейные модули, подключенные по Modbus, для простых приводов с командами "Вверх", "Вниз", "Стоп".

    * Специализированные Zigbee или Z-Wave приводы, поддерживающие точное позиционирование в процентах.

    * DALI или KNX-актуаторы.

    > ℹ️ Информация: В данном уроке предполагается, что базовые настройки оборудования и подключение к MQTT уже выполнены. Мы фокусируемся на логике в Node-RED. Физическое подключение приводов и настройка драйверов рассматриваются в соответствующих модулях по протоколам (Modbus, DALI) и стандартам схем подключения.

    ---

    Проектирование логики: Триггеры, Состояния и Приоритеты

    Простые сценарии вида «если закат, то закрыть шторы» быстро становятся неэффективными, когда вступают в конфликт с желаниями пользователя. Для решения этой проблемы мы применим систему приоритетов, основанную на паттерне 'Manual Override', который мы подробно разобрали в предыдущем уроке.

    Идентификация триггеров

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

    * Восход и закат солнца.

    * Достижение определенного времени (например, закрыть все шторы в 23:00).

    * Показания датчика освещенности (закрыть, если в комнате слишком солнечно).

    * Активация сцены «Кино» (закрыть шторы в гостиной).

    * Активация сцены «Я ушел» (закрыть все шторы в доме).

    * Активация сцены «Доброе утро» (плавно открыть шторы в спальне).

    * Нажатие настенной клавиши.

    * Команда из мобильного приложения.

    * Голосовая команда.

    Важность управления состоянием (State Management)

    Ключ к надежной автоматизации — это управление состоянием. Система всегда должна знать текущее положение каждой шторы. Хранить эту информацию «в уме» у привода недостаточно. Наш центральный мозг, контроллер HI, должен иметь свою копию состояния. Для этой цели мы будем использовать контекст потока (flow context), настроенный на сохранение в файловой системе. Это гарантирует, что состояние не потеряется после перезагрузки контроллера.

    Почему это так важно:

    Состояние может храниться в виде простой строки (`"OPEN"`, `"CLOSED"`) или, что более предпочтительно, в виде объекта для приводов с поддержкой позиционирования:

    {
    

    "state": "OPEN",

    "position": 100

    }

    > 🔗 Связанный материал: Фундаментальный паттерн 'Manual Override', система приоритетов и управление состоянием детально рассмотрены в предыдущих уроках этого модуля (L01 и L02). В этом уроке мы применим эти концепции для управления шторами.

    > 💡 Подсказка: Для хранения состояний, которые должны быть доступны из разных потоков (например, активна ли сцена 'Кино'), используйте глобальный контекст (`global context`). Это упрощает межмодульное взаимодействие и соответствует принципу модульности, когда сценарий управления шторами не должен знать внутреннюю логику сцены "Кино", а лишь реагировать на ее глобальный статус.

    ---

    Практика: Базовая автоматизация по восходу и закату

    рактика: Базовая автоматизация по восходу и закату

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

    > 💡 Связанный материал: Общие принципы работы с узлами, зависящими от времени и геолокации, рассматривались в уроке COURSE-07-M05-L01 «Включение света по движению с учетом освещенности».

    Для генерации событий восхода и заката мы будем использовать популярный узел `node-red-contrib-sun-position` (или его аналог, например, `within-time`).

    Шаг 1: Настройка узла `sun-position`
  • Добавьте узел `sun-position` на холст.
  • Откройте его настройки и введите корректные широту (Latitude) и долготу (Longitude) вашего объекта. Это критически важно для точного расчета времени.
  • Узел будет автоматически генерировать сообщения в моменты восхода (`sunrise`), заката (`sunset`), а также в течение дня. Нам нужны только первые два.
  • Шаг 2: Создание базового потока

    Наш начальный поток будет выглядеть так:

    // FLOW-COMFORT-CURTAIN-001: Sunrise/Sunset Automation
    
    

    [sun-position] -> [switch: "Filter Events"] -> [function: "Prepare Command"] -> [mqtt out: "Send Command"]

    | ^

    +-> [debug: "Ignored Event"] |

    |

    [Catch] -> [function: "Log Error"] -> [file log]

    Шаг 3: Фильтрация событий и подготовка команды
  • Узел `switch` ("Filter Events"):
  • * Настроен на проверку `msg.payload`.

    * Правило 1: `==` (string) `sunrise` -> выход 1

    * Правило 2: `==` (string) `sunset` -> выход 2

    * Все остальные сообщения (например, `night`, `day`) будут игнорироваться.

  • Узел `function` ("Prepare Command"): Этот узел содержит основную логику. Он объединяет обработку событий от обоих выходов узла `switch`.
  •    // Получаем текущий флаг ручного управления из контекста потока.

    // Если он не установлен, считаем его false.

    const manualOverride = flow.get('manual_override') || false;

    if (manualOverride) {

    node.status({fill:"yellow", shape:"dot", text:"Manual override active"});

    // Если активен ручной режим, прерываем автоматику.

    return null;

    }

    let command;

    let newPosition;

    // msg.payload содержит "sunrise" или "sunset" от предыдущего узла switch

    if (msg.payload === 'sunrise') {

    command = "OPEN";

    newPosition = 100;

    } else if (msg.payload === 'sunset') {

    command = "CLOSE";

    newPosition = 0;

    } else {

    // На всякий случай, если придет что-то непредусмотренное

    return null;

    }

    // Сохраняем (прогнозируемое) новое состояние в контексте.

    // Окончательно оно будет подтверждено через механизм обратной связи.

    flow.set('curtain_state', {state: command, position: newPosition});

    // Формируем сообщение для отправки в MQTT, следуя "Контракту сообщения"

    // Мы отправляем команду в общий топик, а не напрямую на привод.

    msg.topic = "hi/devices/curtain_livingroom/set";

    msg.payload = {

    "state": command,

    "source": "automation_sunset",

    "ts": Date.now()

    };

    node.status({fill:"green", shape:"dot", text:`Sent: ${command}`});

    return msg;

    Шаг 4: Отправка команды и сохранение состояния
  • Узел `mqtt out` ("Send Command"): Просто отправляет сформированное в `function` сообщение. Топик уже установлен в `msg.topic`.
  • Сохранение состояния: Обратите внимание, что мы оптимистично обновили состояние в `flow context` с помощью `flow.set()`. В разделе про отказоустойчивость мы сделаем этот механизм более надежным, обновив его по факту получения обратной связи от привода.
  • На этом этапе у нас есть работающая базовая автоматизация, которая уважает ручное управление (хотя сам механизм ручного управления мы еще не реализовали).

    Практика: Интеграция сцен и ручного управления

    Теперь усложним наш поток, добавив обработку ручных команд с настенных клавиш и команд от системных сцен.

    > ⚠️ Внимание: Для физических кнопок всегда используйте узел `rbe` (report by exception) или `delay` в режиме rate limit для подавления «дребезга» и предотвращения отправки дублирующихся команд. Это защищает систему от лишней нагрузки и непредсказуемого поведения.

    Наш поток эволюционирует, принимая входы из разных источников. Все они будут сходиться к общему узлу `switch` для маршрутизации.

    // Входы в систему
    

    [mqtt in: "Ручные команды"] ---+

    |

    [mqtt in: "Сценарные команды"] -+-> [switch: "Маршрутизатор команд"] -> (дальнейшая логика)

    |

    [sun-position] ---------------+

    Шаг 1: Добавление точек входа MQTT
  • Ручное управление:
  • * Добавьте узел `mqtt in`.

    * Топик: `hi/devices/curtain_livingroom/manual/set`

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

    * Пример `msg.payload`: `{"state": "OPEN"}` или `{"position": 75}`.

  • Сценарное управление:
  • * Добавьте еще один узел `mqtt in`.

    * Топик: `hi/scenes/+/set` (используем `+` для подписки на все сцены).

    * Этот узел будет ловить активацию сцен, например, `hi/scenes/cinema/set` с `msg.payload`: `{"state": "ON"}`.

    Шаг 2: Реализация логики приоритетов

    Центральным элементом теперь становится `switch` узел ("Маршрутизатор команд"), который анализирует `msg.topic` и определяет источник.

    Шаг 3: Интеграция механизма `manual_override`

    Поток, идущий от выхода 1 «Маршрутизатора команд» (ручные команды), должен активировать блокировку автоматики, используя паттерн 'Manual Override'.

  • Установка флага: На ветке ручного управления разместите узел `function`, который устанавливает флаг `flow.set('manual_override', true)`.
  • Таймер сброса: Этот же узел `function` должен запустить таймер на автоматический сброс флага (например, через 2 часа с помощью узла `delay`, как мы делали в предыдущем уроке).
  • Сброс флага: По истечении таймера другой узел `function` должен сбросить флаг: `flow.set('manual_override', false)`.
  • Таким образом, любая ручная команда активирует временную блокировку для триггеров с более низким приоритетом. Функциональные узлы на ветках автоматики и сценариев уже содержат проверку этого флага, что обеспечивает соблюдение иерархии приоритетов.

    Шаг 4: Обработка сценарных команд

    На выходе 2 маршрутизатора (`scenes/`) нужен узел `function` для трансляции команды сцены в команду для штор.

    // Проверяем флаг ручного управления. Сцены его уважают.
    

    const manualOverride = flow.get('manual_override') || false;

    if (manualOverride) {

    return null; // Ручное управление активно, сцена игнорируется

    }

    // Пример для сцены "Кино"

    if (msg.topic === 'hi/scenes/cinema/set') {

    let sceneState = msg.payload; // может быть JSON: {"state": "ON"}

    if (typeof msg.payload === 'string') {

    try { sceneState = JSON.parse(msg.payload); } catch(e) {}

    }

    if (sceneState.state === 'ON') {

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

    msg.topic = "hi/devices/curtain_livingroom/set"; // Перенаправляем на исполнительный топик

    return msg;

    }

    }

    // Можно добавить другие else if для других сцен

    return null;

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

    ---

    Отказоустойчивость: Обработка крайних случаев и обратная связь

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

    Проблема перезагрузки контроллера

    Если контроллер HI перезагрузится, `flow context` (если он сохранен в файле) восстановит последнее известное состояние флага `manual_override` и положение штор `curtain_state`. Но что, если во время отключения питания кто-то вручную изменил положение штор? Состояние в контроллере станет неактуальным.

    Решение:
  • Инициализация при старте: Добавьте узел `inject`, настроенный на запуск один раз при старте (`Inject once after 0.1 seconds`).
  • Запрос состояния: Этот узел должен инициировать отправку специальной "опрашивающей" команды в топик `hi/devices/curtain_livingroom/get`. Драйвер привода шторы (в другом потоке), получив это сообщение, должен прочитать реальное состояние привода и опубликовать его в статусный топик.
  • Реализация цикла обратной связи (Feedback Loop)

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

    Архитектура обратной связи:
  • Командный топик: `.../set` (мы в него пишем).
  • Статусный топик: `.../status` (мы из него читаем).
  • Реализация в Node-RED:
  • Добавьте в наш основной поток узел `mqtt in`, подписанный на топик `hi/devices/curtain_livingroom/status`.
  • Подключите его к узлу `function` ("Update State from Feedback").
  •     // Пример сообщения из статусного топика:

    // msg.payload = {"state": "CLOSED", "position": 0, "source": "actuator_feedback"}

    let feedback;

    try {

    // Убедимся, что payload - это объект

    feedback = (typeof msg.payload === 'string') ? JSON.parse(msg.payload) : msg.payload;

    } catch(e) {

    node.error("Invalid JSON in feedback message", msg);

    return null;

    }

    // Проверяем, что в сообщении есть необходимые данные

    if (feedback && (feedback.state !== undefined || feedback.position !== undefined)) {

    // Обновляем состояние в контексте потока

    // Это - ЕДИНСТВЕННЫЙ ИСТОЧНИК ПРАВДЫ о состоянии

    flow.set('curtain_state', feedback);

    node.status({fill:"blue", shape:"dot", text:`Feedback: Pos ${feedback.position}%`});

    // Опционально: можно передать сообщение дальше,

    // чтобы обновить UI в реальном времени

    msg.payload = feedback;

    return msg;

    } else {

    node.warn("Received incomplete feedback", msg);

    return null;

    }

    Теперь, когда автоматика отправляет команду `OPEN`, она лишь инициирует процесс. Реальное изменение `flow.curtain_state` на `OPEN` произойдет только после того, как привод физически откроется и отправит подтверждение в топик `.../status`. Это делает систему невосприимчивой к сбоям связи или поломкам самого привода.

    Логика для промежуточных состояний

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

    ---

    Итоги и дальнейшие шаги

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

    Краткое резюме архитектуры созданного потока:

    Ключевое преимущество созданного решения — его модульность. Теперь для управления шторами из любого другого сценария (например, «Я ушел», «Безопасность» или «Климат-контроль») достаточно отправить одно стандартизированное MQTT-сообщение в топик `hi/devices/curtain_livingroom/set`. Вся сложная логика инкапсулирована внутри нашего потока.

    Идеи для расширения функционала

    На базе созданной архитектуры можно легко добавить новые интеллектуальные функции:

    В следующем уроке мы рассмотрим реализацию комплексной сцены «Кино», которая будет управлять не только шторами, но и освещением, мультимедиа и климатом, используя созданные нами модульные компоненты.