SCN-COMFORT-007: Сцена 'Кино' (закрытие штор, приглушение света)
Введение в комплексные сценарии: Сцена 'Кино'
Комплексный (или композитный) сценарий — это автоматизированная последовательность действий, которая для своего выполнения затрагивает несколько разнородных инженерных подсистем объекта. В отличие от простых сценариев, таких как «включить свет по движению», комплексные сценарии управляют целой средой, создавая определённую атмосферу или подготавливая помещение к конкретной деятельности.Цель сценария «Кино», который мы реализуем в этом уроке, — мгновенное создание комфортной атмосферы для просмотра фильма. Одним действием — нажатием кнопки, голосовой командой или выбором в приложении — пользователь инициирует скоординированную работу систем освещения и солнцезащиты.
Как и любой другой сценарий, «Кино» состоит из трёх фундаментальных компонентов:
Важнейшим предварительным условием для создания таких сценариев является качественная проработка проектной документации и системы именования. Когда светильник в гостиной называется `LIGHT-LIVINGROOM-MAIN`, а привод штор — `SHADE-LIVINGROOM-WEST`, создание логики в Node-RED становится интуитивно понятным процессом. Если же устройства именуются как `DALI_addr_5` или `Relay_12`, поддержка и отладка системы превращается в сложную задачу, требующую постоянного обращения к таблицам и схемам. Правильное именование — это инвестиция в будущую стабильность и масштабируемость вашего проекта.
---
Агрегация устройств и подготовка топиков MQTT
Для того чтобы наш логический блок в Node-RED мог управлять устройствами, ему нужен единый, стандартизированный язык. На физическом уровне устройства могут «говорить» на разных языках: свет управляется по DALI или KNX, а шторы — через релейный модуль по Modbus. Роль универсального языка-посредника в нашей архитектуре выполняет протокол MQTT.
Задача инженера — создать абстракцию устройства. Это означает, что для Node-RED не должно быть разницы, какой протокол используется «под капотом». Поток отправляет стандартизированную MQTT-команду, а специализированные шлюзы (другие потоки Node-RED) транслируют её в соответствующий протокол.
🔗 Связанный материал: Подробная информация по настройке шлюзов и формированию топиков MQTT для различных протоколов раскрыта в Курсе 04: «Интеграция сторонних систем». См. уроки COURSE-04-M03-L02 (KNX/DALI) и COURSE-04-M02-L04 (Modbus RTU).
Структура MQTT-топиков
Для управления нашим сценарием мы определим единую точку входа — триггерный топик. Для управления конечными устройствами мы также будем использовать чёткую и понятную структуру.
| Назначение | MQTT-топик | Пример `msg.payload` | Описание |
| :--- | :--- | :--- | :--- |
| Триггер сценария | `hi/living_room/scene/cinema/set` | `"TOGGLE"` | Команда для активации/деактивации сцены. |
| Управление светом| `hi/living_room/light/main/set` | `{"brightness": 20, "fade_time": 5}` | Команда для DALI-диммера: установить яркость 20% в течение 5 секунд. |
| Управление шторами| `hi/living_room/shades/west/set`| `"CLOSE"` | Команда для привода штор: закрыть. |
Такой подход позволяет нам в главном сценарии оперировать только логическими сущностями (`scene`, `light`, `shades`), не задумываясь о деталях реализации. Например, поток, отвечающий за шторы, подписывается на топик `hi/living_room/shades/west/set`. Получив сообщение с `payload` равным `"CLOSE"`, он формирует и отправляет specific-команду на Modbus-устройство, которое физически управляет приводом.
Формирование `msg.payload`
Ключевым аспектом является соблюдение контракта сообщения. Для каждого типа устройства мы заранее определяем, в каком формате оно ожидает получить команду.
- DALI-диммеры: Современные DALI-шлюзы, интегрированные через MQTT, часто ожидают получить JSON-объект. Это позволяет передавать несколько параметров в одном сообщении, например, целевую яркость и время диммирования (fade time).
{
"state": "ON",
"brightness": 20,
"fade_time": 5
}
- Приводы штор (Modbus/Реле): Для устройств с простой логикой (открыть/закрыть/стоп) достаточно строковых команд. Это упрощает и отладку, и интеграцию.
"CLOSE"
Подготовив такую архитектуру MQTT-топиков и контрактов сообщений, мы можем приступать к созданию основного потока сценария «Кино», который будет выполнять роль дирижёра этого оркестра устройств.
---
Построение потока в Node-RED: от триггера к действию
На первом этапе мы создадим простой, бесстатусный (stateless) поток. Его задача — при получении сообщения в триггерный топик отправить предопределенные команды на устройства.
💡 Подсказка: Для отладки сценария размещайте узел `debug` после каждого узла `change` или `function`. Это позволит в реальном времени видеть, какой `msg.payload` отправляется на исполнительное устройство, и быстро выявлять ошибки в его структуре.
ASCII-схема потока (FLOW-SCENE-CINEMA-001): +----------------+
[mqtt in]-------->| switch |-----+----->[change: Set Light Payload]----->[mqtt out: To Light]
hi/living_room/ | "TOGGLE" | |
scene/cinema/set +----------------+ +----->[change: Set Shades Payload]---->[mqtt out: To Shades]
Шаг 1: Настройка триггера
* Server: Выберите ваш MQTT-брокер (например, `localhost:1883`).
* Topic: `hi/living_room/scene/cinema/set`
* QoS: `1` — для гарантированной, но не дублирующейся доставки.
* Name: `Trigger: Scene Cinema`
Шаг 2: Ветвление логики
Мы хотим, чтобы одно событие порождало несколько параллельных действий. Для этого идеально подходит узел `switch`. Хотя в данном простом случае можно обойтись и без него (просто разветвив провод), его использование делает поток более читаемым и готовым к будущим усложнениям (например, если мы захотим передавать в `payload` не `"TOGGLE"`, а `"ACTIVATE"` или `"DEACTIVATE"`).
* Property: `msg.payload`
* Правило 1: `==` (string) `TOGGLE`. Добавьте один выход для этого правила.
* Name: `Check Command`
Шаг 3: Формирование команд для акторов
Теперь для каждой ветки мы создадим свой `msg.payload` и `msg.topic` с помощью узла `change`.
Ветка 1: Управление светом* Правило 1 (Установка Payload):
* `Set`: `msg.payload`
* `to the value`: (JSON) `{"brightness": 20, "fade_time": 5}`
* Правило 2 (Установка Топика):
* `Set`: `msg.topic`
* `to the value`: (string) `hi/living_room/light/main/set`
* Name: `Payload: Light Dim`
Ветка 2: Управление шторами* Правило 1 (Установка Payload):
* `Set`: `msg.payload`
* `to the value`: (string) `"CLOSE"`
* Правило 2 (Установка Топика):
* `Set`: `msg.topic`
* `to the value`: (string) `hi/living_room/shades/west/set`
* Name: `Payload: Shades Close`
Шаг 4: Отправка команд
Финальный шаг — отправка сформированных сообщений в MQTT.
Теперь, при отправке в топик `hi/living_room/scene/cinema/set` сообщения `"TOGGLE"`, наш поток отправит две команды: одну на приглушение света, вторую на закрытие штор. Однако этот поток имеет серьезный недостаток: он умеет только активировать сцену. Чтобы ее деактивировать, потребуется отдельная логика. Эту проблему мы решим в следующей секции.
---
Реализация состояния (Stateful) и логики 'Toggle'
Профессиональный сценарий должен быть stateful (с отслеживанием состояния), чтобы одна и та же команда могла выполнять разные действия. В нашем случае, команда `"TOGGLE"` должна активировать сцену «Кино», если она выключена, и деактивировать, если она уже включена. Это называется логикой Toggle (переключения).
Для отслеживания состояния сцены мы будем использовать контекстное хранилище Node-RED (`flow context`), которое может быть настроено на сохранение данных между перезагрузками. Принципы работы с контекстом подробно рассмотрены в уроке COURSE-07-M01-L05.
⚠️ Внимание: При деактивации сцены не просто включайте свет на 100%, а возвращайте его к предыдущему состоянию. Для этого перед изменением яркости сохраните текущие параметры светильников в `flow context`. Это сделает автоматизацию более комфортной для пользователя и является признаком качественной проработки сценария.
Алгоритм логики 'Toggle'
Центральным элементом нашего обновленного потока станет узел `function`, который будет содержать всю логику.
a. Сохранить текущее состояние света в контекст (например, `flow.set('previous_light_state', ...)`).
b. Сформировать команды для активации сцены (приглушить свет, закрыть шторы).
c. Сохранить новое состояние в контекст: `flow.set('is_cinema_active', true);`.
a. Сформировать команды для деактивации сцены (вернуть свет к сохраненному состоянию, открыть шторы).
b. Сохранить новое состояние в контекст: `flow.set('is_cinema_active', false);`.
Практическая реализация
Мы заменим узел `switch` и два узла `change` на один узел `function`.
ASCII-схема потока (FLOW-SCENE-CINEMA-002): +--------------------+
[mqtt in]-------->| function: |----(Выход 1)----> [mqtt out: To Light]
hi/living_room/ | Toggle Cinema |
scene/cinema/set | State Logic |----(Выход 2)----> [mqtt out: To Shades]
+--------------------+
Код для узла `function`:
- Имя: `Toggle Cinema State`
- Выходы: 2 (первый для света, второй для штор)
- Код:
// Получаем текущее состояние сцены из контекста.
// Если переменная не существует (первый запуск), считаем сцену неактивной (false).
let isCinemaActive = flow.get('is_cinema_active') || false;
// Читаем из контекста последнее известное состояние света.
// Для полноценной работы требуется отдельный поток, который слушает топик состояния
// светильника и сохраняет его параметры в 'last_known_light_state'.
// Если данных нет, используем значение по умолчанию.
let previousLightState = flow.get('last_known_light_state') || { brightness: 80 };
let lightMsg = { topic: 'hi/living_room/light/main/set' };
let shadesMsg = { topic: 'hi/living_room/shades/west/set' };
if (!isCinemaActive) {
// === АКТИВАЦИЯ СЦЕНЫ "КИНО" ===
// 1. Формируем сообщение для приглушения света
lightMsg.payload = {
brightness: 20,
fade_time: 5
};
// Устанавливаем статус узла для визуальной диагностики
node.status({ fill: "blue", shape: "dot", text: "Активация. Свет -> 20%" });
// 2. Формируем сообщение для закрытия штор
shadesMsg.payload = "CLOSE";
// 3. Обновляем состояние в контексте
flow.set('is_cinema_active', true);
} else {
// === ДЕАКТИВАЦИЯ СЦЕНЫ "КИНО" ===
// 1. Формируем сообщение для восстановления света
// Возвращаем яркость к значению, которое было до активации сцены.
lightMsg.payload = {
brightness: previousLightState.brightness,
fade_time: 3
};
// Устанавливаем статус узла
node.status({ fill: "yellow", shape: "ring", text: "Деактивация. Свет -> " + previousLightState.brightness + "%" });
// 2. Формируем сообщение для открытия штор
shadesMsg.payload = "OPEN";
// 3. Обновляем состояние в контексте
flow.set('is_cinema_active', false);
}
// Отправляем сообщения на соответствующие выходы
// Выход 1: Свет
// Выход 2: Шторы
return [lightMsg, shadesMsg];
Этот подход гораздо более гибкий и мощный. Он не только реализует логику переключения, но и делает сценарий информативным благодаря узлу `node.status`, который визуально отображает текущее действие прямо в редакторе Node-RED.
---
Итоги и возможные улучшения сценария
В этом уроке мы спроектировали и реализовали комплексный сценарий «Кино», пройдя путь от простой stateless-логики до stateful-реализации с отслеживанием состояния.
Краткий обзор созданного потока:Мы создали поток (`FLOW-SCENE-CINEMA-002`), который по единой MQTT-команде (`"TOGGLE"`) выполняет скоординированные действия над несколькими подсистемами. Центральным элементом является узел `function`, который, используя `flow context` для хранения состояния, решает, активировать или деактивировать сцену. Это позволило нам реализовать интуитивно понятную для пользователя логику переключения.
📋 Ключевые понятия:
- Комплексные (композитные) сценарии: Управление несколькими подсистемами для создания нужной атмосферы.
- Абстракция устройств: Использование MQTT как универсального языка для управления устройствами с разными протоколами.
- Управление состоянием (Stateful): Отслеживание состояния сценария (`активен`/`неактивен`) для реализации сложной логики.
- Логика 'Toggle': Активация и деактивация сценария одной и той же командой.
- Контекстное хранилище (`flow context`): Механизм Node-RED для сохранения переменных в рамках одного потока, в том числе между перезагрузками контроллера.
Идеи для расширения и улучшения
Созданный нами сценарий является отличной основой, которую можно и нужно развивать:
* Добавить в сценарий активации команду на включение проектора и AV-ресивера (например, по IP-протоколу, RS-232 или через ИК-шлюз, абстрагированный через MQTT).
* `msg.topic`: `hi/living_room/projector/set`, `msg.payload`: `"ON"`.
* При деактивации сцены (например, по выключению проектора) — запускать обратную последовательность.
* При активации сцены «Кино» переводить систему вентиляции в тихий ночной режим, чтобы шум не мешал просмотру.
* `msg.topic`: `hi/living_room/climate/hvac/set_profile`, `msg.payload`: `"QUIET"`.
* При активации «Кино» необходимо временно отключать другие сценарии в этой же комнате. Например, сценарий «Включение света по движению» (`SCN-LIGHT-001`) должен быть заблокирован, чтобы случайное движение не включало основной свет на полную яркость. Это можно реализовать через глобальный флаг в `global context` (например, `global.set('living_room_scene_lock', true)`).
* Интегрировать голосовых ассистентов (Яндекс.Алиса, Google Assistant), которые будут отправлять то же самое MQTT-сообщение в триггерный топик по команде «Алиса, включи кино».
Эти улучшения превращают простой набор действий в по-настоящему интеллектуальную и комфортную систему, которая предвосхищает потребности пользователя и безупречно работает как единое целое.
Что дальше
В следующем уроке мы рассмотрим еще один важный комплексный сценарий — `SCN-SAFETY-001: Имитация присутствия`, который повышает безопасность дома во время отсутствия хозяев, хаотично включая и выключая свет в разных комнатах.