🧘 Сценарий медитации в Node-RED: как Алиса, свет и музыка помогают расслабиться
Представьте: вы возвращаетесь с работы, устали, голова гудит. Вы просто говорите: «Алиса, отдых». И мир вокруг начинает меняться.
Свет в комнате приглушается до тёплого мягкого свечения — 30% яркости, цвет становится похожим на закат. Алиса приятным голосом сообщает: «Хорошо, создаю атмосферу для отдыха».
Система проверяет погоду за окном: если температура выше 18°C и нет снега с туманом — Алиса предлагает открыть окно для свежего воздуха. Если окно уже открыто или погода плохая — сразу запускает музыку. А если окно открыто в непогоду — вежливо попросит закрыть.
Через 30 минут, когда вы уже расслабились, музыка сама выключается, а свет плавно возвращается к обычному режиму. Всё автоматически, без лишних движений.
В этой статье: мы соберём полноценный сценарий медитации в Node-RED с нуля — голосовое управление через Алису, интеграция с погодой, датчиком окна и умным освещением.
Содержание статьи
1. Подготовка Home Assistant
Шаг 1.1. Установите интеграцию Yandex Station Intents
- Откройте HACS → Интеграции
- Нажмите на три точки (⋮) → Пользовательские репозитории
- Добавьте репозиторий:
https://github.com/dext0r/ha-yandex-station-intents, тип: Интеграция - Найдите интеграцию Yandex Station Intents → Скачать
- Перезагрузите Home Assistant
Шаг 1.2. Добавьте в configuration.yaml
yandex_station_intents:
intents:
отдых:
say_phrase: "Хорошо, создаю атмосферу для отдыха"
Шаг 1.3. Перезагрузите YAML
- Панель разработчика → YAML → Перезагрузить
2. Подготовка Node-RED
Шаг 2.1. Установите необходимые узлы
- Откройте Node-RED → Меню (☰) → Manage palette → вкладка Install
- Установите:
node-red-contrib-home-assistant-websocket(если ещё не установлен) - Установите:
node-red-contrib-light-transition(для плавного изменения света)
Шаг 2.2. Подключите Home Assistant
- Перетащите любой узел HA (например, current state) на рабочую область
- Дважды кликните → нажмите на значок карандаша ✏️ рядом с Server
- Укажите URL (например,
http://192.168.1.50:8123) и токен доступа (долгосрочный токен из профиля HA) - Нажмите Add → Done
3. Создание сценария (пошагово)
🔹 Шаг 1. Узел «Слушаем Алису»
- Тип узла:
events: all(Home Assistant) - Событие:
yandex_intent
🔹 Шаг 2. Узел «Проверка команды»
- Тип узла:
switch - Свойство:
payload.event.text - Правило:
== отдых
🔹 Шаг 3. Узел «Приглушенный свет»
- Тип узла:
action(Home Assistant) - Домен:
light, Сервис:turn_on - Данные (JSON):
{"brightness_pct": 30, "color_name": "warm_white"}
🔹 Шаг 4. Узел «Узнаем погоду»
- Тип узла:
current state(Home Assistant) - Entity ID:
weather.forecast_home_assistant(ваш погодный сервис)
🔹 Шаг 5. Узел «Анализ погоды»
- Тип узла:
function
let temp = msg.data.attributes.temperature;
let condition = msg.data.state;
let min_temp = 18;
let bad_weather = ["snowy", "snow", "blizzard", "fog", "foggy", "mist"];
let temp_ok = (temp >= min_temp);
let weather_ok = !bad_weather.includes(condition);
node.warn(`🌡️ ${temp}°C, ${condition} → открываем: ${temp_ok && weather_ok}`);
if (temp_ok && weather_ok) {
msg.payload = "open";
} else {
msg.payload = "skip";
}
msg.topic = "weather";
return msg;
🔹 Шаг 6. Узел «Сохраняем значение»
- Тип узла:
change - Действие:
Set flow.need to msg.payload
🔹 Шаг 7. Узел «Узнаем состояние окна»
- Тип узла:
current state(Home Assistant) - Entity ID:
binary_sensor.test_product_dver_1(ваш датчик окна)
🔹 Шаг 8. Узел «Сравнение и решение»
- Тип узла:
function
let need = flow.get("need") || "skip";
let current_raw = msg.data?.state || msg.payload?.state || msg.payload;
let current = "unknown";
if (current_raw === "on" || current_raw === "open") {
current = "open";
} else if (current_raw === "off" || current_raw === "closed") {
current = "closed";
}
node.warn(`🔍 Нужно: ${need}, Состояние окна: ${current}`);
if (need === "open" && current === "open") {
return { payload: "ok" };
}
else if (need === "skip" && current === "closed") {
return { payload: "ok" };
}
else if (need === "open" && current === "closed") {
return { payload: "need_open" };
}
else if (need === "skip" && current === "open") {
return { payload: "need_close" };
}
else {
return { payload: "ok" };
}
🔹 Шаг 9. Узел «Открыть окно?»
- Тип узла:
switch - Правило 1:
== ok→ «Продолжаем» - Правило 2:
== need_open→ ветка открытия окна - Правило 3:
== need_close→ ветка закрытия окна
🔹 Шаг 10. Ветка «need_open» (открыть окно)
- Узел «Алиса — нужно открыть окно»:
action(tts.cloud_say), сообщение «На улице комфортная температура, можно открыть окно. Ожидаю на открытие 1 минуту.» - Узел «Таймер 1 минута»:
delay60 секунд - Узел «Проверка открытия окна»:
current state, Halt if:on, For: 2 секунды - Узел «Алиса — окно не открыто»:
action(tts.cloud_say), «Продолжаю без проветривания» (по таймауту)
🔹 Шаг 11. Ветка «need_close» (закрыть окно)
- Узел «Алиса — нужно закрыть окно»:
action(tts.cloud_say), сообщение «Погода изменилась, необходимо закрыть окно. Ожидаю на закрытие 1 минуту.» - Узел «Таймер 1 минута»:
delay60 секунд - Узел «Проверка закрытия окна»:
current state, Halt if:off, For: 2 секунды - Узел «Алиса — окно не закрыто»:
action(tts.cloud_say), «Вы можете замерзнуть с открытым окном»
Если у вас установлен привод (электромеханизм) для автоматического открытия/закрытия окна, замените узлы с ожиданием действий пользователя на прямое управление приводом:
- Вместо узла «Алиса — нужно открыть окно» добавьте узел
actionс сервисомcover.open_cover(или своей автоматизацией) - Таймер ожидания можно убрать — привод сам сообщит о конечном положении через датчик
- Для закрытия окна используйте
cover.close_cover
🔹 Шаг 12. Узел «Продолжаем» (change)
- Тип узла:
change - Действие:
Set msg.payload to "continue"
🔹 Шаг 13. Узел «Запуск музыки»
- Тип узла:
action - Домен:
media_player, Сервис:play_media - Данные (JSON):
{"media_content_id": 5616415, "media_content_type": "album"}
- Альбом:
{"media_content_id": 5616415, "media_content_type": "album"}— ID альбома из ссылки Яндекс.Музыки - Трек:
{"media_content_id": 12345678, "media_content_type": "track"} - Подкаст:
{"media_content_id": "название_подкаста", "media_content_type": "podcast"} - Плейлисты (не поддерживаются): Яндекс Станция не поддерживает воспроизведение плейлистов через API
🔹 Шаг 14. Узел «30 минут релакса»
- Тип узла:
delay - Задержка: 30 минут
- Выход 1 → «Остановить музыку»
- Выход 2 → «Плавное включение света»
🔹 Шаг 15. Узел «Остановить музыку»
- Тип узла:
action - Домен:
media_player, Сервис:media_stop
🔹 Шаг 16. Узел «плавное включение света»
- Тип узла:
light-transition - Start Bright: 30, End Bright: 255, Brightness Type: Percent
- Transition Time: 20 Second, Steps: 50
- End RGB:
#7b219f(фиолетовый)
🔹 Шаг 17. Узел «Включение света» (финальный)
- Тип узла:
action - Данные (JSONata):
{"brightness_pct": msg.payload.brightness_pct, "color_name": "purple"}
light-transition необходимо предварительно установить библиотеку node-red-contrib-light-transition через менеджер палитры Node-RED.
4. Импорт готового JSON
- Замените
light.kolco2на ID вашего светового устройства - Замените
media_player.yandex_stationна ID вашей колонки Алисы - Замените
binary_sensor.test_product_dver_1на ID вашего датчика окна - Проверьте, что сервер Home Assistant в узлах настроен на ваш
- Нажмите Deploy
[
{
"id": "72d29f7330dee409",
"type": "tab",
"label": "Медитация",
"disabled": false
},
{
"id": "30b8bcdf24086c67",
"type": "server-events",
"name": "Слушаем Алису",
"eventType": "yandex_intent",
"x": 200,
"y": 260,
"wires": [["f92699ead37b9486"]]
},
{
"id": "f92699ead37b9486",
"type": "switch",
"name": "Проверка команды",
"property": "payload.event.text",
"rules": [{"t": "eq", "v": "отдых", "vt": "str"}],
"x": 420,
"y": 260,
"wires": [["802cb848ebc745e3"]]
},
{
"id": "802cb848ebc745e3",
"type": "api-call-service",
"name": "Приглушенный свет",
"action": "light.turn_on",
"entityId": ["light.kolco2"],
"data": "{ \"brightness_pct\": 30, \"color_name\": \"warm_white\" }",
"x": 680,
"y": 260,
"wires": [["782a13dc94615628"]]
},
{
"id": "782a13dc94615628",
"type": "api-current-state",
"name": "Узнаем погоду",
"entity_id": "weather.forecast_home_assistant",
"x": 900,
"y": 260,
"wires": [["9ac6b3bc65a71fe5"]]
},
{
"id": "9ac6b3bc65a71fe5",
"type": "function",
"name": "Анализ погоды",
"func": "let temp = msg.data.attributes.temperature;\nlet condition = msg.data.state;\nlet min_temp = 18;\nlet bad_weather = [\"snowy\",\"snow\",\"blizzard\",\"fog\",\"foggy\",\"mist\"];\nlet temp_ok = (temp >= min_temp);\nlet weather_ok = !bad_weather.includes(condition);\nif (temp_ok && weather_ok) {\n msg.payload = \"open\";\n} else {\n msg.payload = \"skip\";\n}\nmsg.topic = \"weather\";\nreturn msg;",
"x": 1080,
"y": 260,
"wires": [["06242f8f9053d827"]]
},
{
"id": "06242f8f9053d827",
"type": "change",
"name": "Сохраняем значение",
"rules": [{"t": "set", "p": "need", "pt": "flow", "to": "payload", "tot": "msg"}],
"x": 1280,
"y": 260,
"wires": [["cd5cfa29faaf8f81"]]
},
{
"id": "cd5cfa29faaf8f81",
"type": "api-current-state",
"name": "Узнаем состояние окна",
"entity_id": "binary_sensor.test_product_dver_1",
"x": 950,
"y": 320,
"wires": [["a826e51c424ddac2"]]
},
{
"id": "a826e51c424ddac2",
"type": "function",
"name": "Сравнение и решение",
"func": "let need = flow.get(\"need\") || \"skip\";\nlet current_raw = msg.data?.state || msg.payload?.state || msg.payload;\nlet current = \"unknown\";\nif (current_raw === \"on\" || current_raw === \"open\") { current = \"open\"; }\nelse if (current_raw === \"off\" || current_raw === \"closed\") { current = \"closed\"; }\nif (need === \"open\" && current === \"open\") { return { payload: \"ok\" }; }\nelse if (need === \"skip\" && current === \"closed\") { return { payload: \"ok\" }; }\nelse if (need === \"open\" && current === \"closed\") { return { payload: \"need_open\" }; }\nelse if (need === \"skip\" && current === \"open\") { return { payload: \"need_close\" }; }\nelse { return { payload: \"ok\" }; }",
"x": 1190,
"y": 320,
"wires": [["b716c2d13c920d94"]]
},
{
"id": "b716c2d13c920d94",
"type": "switch",
"name": "Открыть окно?",
"property": "payload",
"rules": [
{"t": "eq", "v": "ok", "vt": "str"},
{"t": "eq", "v": "need_open", "vt": "str"},
{"t": "eq", "v": "need_close", "vt": "str"}
],
"x": 1400,
"y": 320,
"wires": [["c05a1adf91e72627"], ["876e6e043a6fefc3"], ["1ab13e873953f03c"]]
},
{
"id": "876e6e043a6fefc3",
"type": "api-call-service",
"name": "Алиса - нужно открыть окно",
"action": "tts.cloud_say",
"data": "{\"entity_id\":\"media_player.yandex_station\",\"message\":\"На улице комфортная температура, можно открыть окно. Ожидаю на открытие 1 минуту.\"}",
"x": 350,
"y": 480,
"wires": [["664d425e78e101d4"]]
},
{
"id": "1ab13e873953f03c",
"type": "api-call-service",
"name": "Алиса - нужно закрыть окно",
"action": "tts.cloud_say",
"data": "{\"entity_id\":\"media_player.yandex_station\",\"message\":\"Погода изменилась, необходимо закрыть окно. Ожидаю на закрытие 1 минуту.\"}",
"x": 350,
"y": 520,
"wires": [["de30daaf2439a307"]]
},
{
"id": "664d425e78e101d4",
"type": "delay",
"name": "Таймер 1 минута",
"timeout": "60",
"timeoutUnits": "seconds",
"x": 590,
"y": 480,
"wires": [["2af9997f6db57bf5"]]
},
{
"id": "de30daaf2439a307",
"type": "delay",
"name": "Таймер 1 минута",
"timeout": "60",
"timeoutUnits": "seconds",
"x": 590,
"y": 540,
"wires": [["dc8514b8b30d18e8"]]
},
{
"id": "2af9997f6db57bf5",
"type": "api-current-state",
"name": "Проверка открытия окна",
"entity_id": "binary_sensor.test_product_dver_1",
"halt_if": "on",
"for": "2",
"forUnits": "seconds",
"x": 620,
"y": 420,
"wires": [["c05a1adf91e72627"], ["547d1b443dd11a8c"]]
},
{
"id": "547d1b443dd11a8c",
"type": "api-call-service",
"name": "Алиса - окно не открыто",
"action": "tts.cloud_say",
"data": "{\"entity_id\":\"media_player.yandex_station\",\"message\":\"Продолжаю без проветривания\"}",
"x": 920,
"y": 420,
"wires": [["c05a1adf91e72627"]]
},
{
"id": "dc8514b8b30d18e8",
"type": "api-current-state",
"name": "Проверка закрытия окна",
"entity_id": "binary_sensor.test_product_dver_1",
"halt_if": "off",
"for": "2",
"forUnits": "seconds",
"x": 600,
"y": 600,
"wires": [["c05a1adf91e72627"], ["1fbb4e16acc8e5cd"]]
},
{
"id": "1fbb4e16acc8e5cd",
"type": "api-call-service",
"name": "Алиса - окно не закрыто",
"action": "tts.cloud_say",
"data": "{\"entity_id\":\"media_player.yandex_station\",\"message\":\"Вы можете замерзнуть с открытым окном\"}",
"x": 900,
"y": 600,
"wires": [["c05a1adf91e72627"]]
},
{
"id": "c05a1adf91e72627",
"type": "api-call-service",
"name": "Запуск альбома Алиса",
"action": "media_player.play_media",
"entityId": ["media_player.yandex_station"],
"data": "{\"media_content_id\":5616415,\"media_content_type\":\"album\"}",
"x": 1110,
"y": 520,
"wires": [["12d4d55474589aab"]]
},
{
"id": "12d4d55474589aab",
"type": "delay",
"name": "30 минут релакса",
"timeout": "30",
"timeoutUnits": "minutes",
"x": 1330,
"y": 520,
"wires": [["00373bb6b7642f46", "2e72d44181c3b924"]]
},
{
"id": "2e72d44181c3b924",
"type": "api-call-service",
"name": "Остановить музыку",
"action": "media_player.media_stop",
"entityId": ["media_player.yandex_station"],
"x": 1360,
"y": 460,
"wires": [[]]
},
{
"id": "00373bb6b7642f46",
"type": "light-transition",
"name": "плавное включение света",
"endRGB": "#7b219f",
"transitionTime": "20",
"transitionTimeUnits": "Second",
"steps": "50",
"startBright": "30",
"endBright": "255",
"brightnessType": "Percent",
"x": 1360,
"y": 600,
"wires": [["ac430cd4940073ff"]]
},
{
"id": "ac430cd4940073ff",
"type": "api-call-service",
"name": "Включение света",
"action": "light.turn_on",
"entityId": ["light.kolco2"],
"data": "{\"brightness_pct\": msg.payload.brightness_pct, \"color_name\": \"purple\"}",
"dataType": "jsonata",
"x": 1330,
"y": 660,
"wires": [[]]
}
]