ГлавнаяАкадемияСценарии умного дома: режимы, состояния, приоритеты → Практика: Добавление режима 'Ночь' и управление ночным освещением

Практика: Добавление режима 'Ночь' и управление ночным освещением

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

Введение: Расширение графа состояний режимом 'Ночь'

Ранее в курсе мы спроектировали и реализовали базовую, но крайне важную систему управления режимами «Дома» и «Нет дома». Эта система является ядром для большинства сценариев автоматизации, позволяя контроллеру понимать глобальный контекст происходящего на объекте. Она определяет, нужно ли экономить энергию, активировать охранные функции или обеспечивать максимальный комфорт для присутствующих людей.

Однако бинарной логики "кто-то есть / никого нет" часто бывает недостаточно для создания по-настоящему интеллектуальной и комфортной среды. День и ночь внутри дома — это два совершенно разных мира с разными требованиями к освещению, климату и безопасности. Именно для этого вводится третье ключевое состояние — режим «Ночь».

Необходимость режима «Ночь» продиктована тремя основными факторами:

  • Комфорт: Ночью яркий свет не просто не нужен, но и может быть вреден. Включение освещения в коридоре или ванной комнате на 100% яркости может ослепить и нарушить сон. Режим «Ночь» позволяет реализовать сценарии приглушенного, "дежурного" освещения.
  • Энергоэффективность: В ночное время можно отключить еще больше потребителей, чем в режиме «Нет дома» (например, декоративную подсветку, некоторые розетки), а также перевести систему вентиляции в минимальный режим работы.
  • Безопасность: Поведение охранных датчиков ночью может отличаться. Например, датчики движения внутри дома, которые игнорируются в режиме «Дома», могут быть переведены в режим "тревоги" или "уведомления" в режиме «Ночь», сообщая о непредвиденной активности.
  • Цель этого урока — расширить наш существующий конечный автомат (FSM), добавив в него состояние «Ночь». Мы последовательно выполним следующие шаги:

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

    ---

    Модификация Subflow для поддержки режима 'Ночь'

    одификация Subflow для поддержки режима 'Ночь'

    Основой нашей системы управления режимами является переиспользуемый компонент — Subflow, который мы назвали `subflow-modes`. Этот "черный ящик" отвечает за валидацию команд, смену состояний и публикацию текущего режима для всей системы. Сейчас мы внесем в него изменения, чтобы добавить поддержку режима «Ночь».

    > ⚠️ Внимание: Перед внесением изменений в рабочий subflow настоятельно рекомендуется создать его резервную копию. Экспортируйте текущую версию subflow в JSON-файл через меню Node-RED (☰ -> Export -> Selected Nodes). Это позволит быстро вернуться к рабочей версии в случае ошибки.

    Пошаговое редактирование Subflow

  • Найдите в палитре и откройте ваш `subflow-modes`. Внутри вы увидите логику, построенную на узлах `Function`, `Switch`, `Change` и `mqtt out`. Нас интересует основной узел `Function`, который реализует логику конечного автомата.
  • Откройте на редактирование этот узел. Его текущий код обрабатывает переходы только между `home` и `away`. Нам нужно добавить `night` как возможное состояние и определить правила перехода.
  • Ключевое логическое правило: в режим «Ночь» можно перейти только из режима «Дома». Нелогично активировать ночной режим, если дома никого нет. Аналогично, выход из режима «Ночь» должен переводить систему обратно в «Дома».

    Обновление кода в узле Function

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

    1. Расширьте список разрешенных режимов. Найдите строку с `allowedModes` и добавьте в массив `'night'`:
    const allowedModes = ['home', 'away', 'night'];
    
    2. Добавьте правило перехода в 'Ночь'. Сразу после блока `if (requestedMode === currentMode) { ... }`, добавьте новую проверку:
    // Новое правило: переход в 'night' возможен только из 'home'
    

    if (requestedMode === 'night' && currentMode !== 'home') {

    node.warn(`Cannot switch to 'night' mode from '${currentMode}' mode. Ignoring.`, msg);

    node.status({ fill: "yellow", shape: "ring", text: `Ignored: night from ${currentMode}` });

    return null;

    }

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

    // Код для урока COURSE-07-M02-L01
    

    // Получаем запрашиваемый режим из входящего сообщения

    const requestedMode = msg.payload;

    // --- 1. Валидация входящей команды ---

    // Расширяем список разрешенных режимов

    const allowedModes = ['home', 'away', 'night'];

    if (!allowedModes.includes(requestedMode)) {

    node.error(`Invalid mode requested: ${requestedMode}`, msg);

    node.status({ fill: "red", shape: "dot", text: `Error: Invalid mode '${requestedMode}'` });

    return null; // Прерываем выполнение потока

    }

    // Получаем текущий режим из контекста потока (flow context)

    // Мы используем персистентный контекст, чтобы режим сохранялся после перезагрузки

    const currentMode = flow.get('current_mode', 'file') || 'away';

    // --- 2. Логика конечного автомата (FSM) ---

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

    if (requestedMode === currentMode) {

    node.status({ fill: "grey", shape: "dot", text: `Mode already '${currentMode}'` });

    return null;

    }

    // Новое правило: переход в 'night' возможен только из 'home'

    if (requestedMode === 'night' && currentMode !== 'home') {

    node.warn(`Cannot switch to 'night' mode from '${currentMode}' mode. Ignoring.`, msg);

    node.status({ fill: "yellow", shape: "ring", text: `Ignored: night from ${currentMode}` });

    return null;

    }

    // --- 3. Если все проверки пройдены, меняем состояние ---

    flow.set('current_mode', requestedMode, 'file');

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

    // Это сообщение станет источником правды для всей системы

    msg.payload = requestedMode;

    msg.topic = "hi/modes/current"; // Топик состояния

    msg.retain = true; // Флаг retain обязателен, чтобы новые устройства сразу получали текущий режим

    node.status({ fill: "blue", shape: "dot", text: `Switched to '${requestedMode}'` });

    return msg;

    Ключевые изменения в коде:

    После сохранения кода закройте редактор subflow и не забудьте нажать кнопку Deploy.

    Тестирование Subflow

    Для проверки создайте временный тестовый поток:

  • Возьмите три узла `Inject`. Настройте каждый на отправку `msg.payload` (string) со значениями `home`, `away` и `night`.
  • Подключите их ко входу вашего `subflow-modes`.
  • Подключите выход `subflow-modes` к узлу `Debug`.
  • Проведите тесты:

    Это подтверждает, что ваша логика конечного автомата работает корректно.

    Триггеры для смены режима: по времени и вручную

    Теперь, когда наш `subflow-modes` готов обрабатывать новый режим, нам нужны "пульты управления" — триггеры, которые будут отправлять ему команды. Мы создадим два основных типа триггеров: автоматический (по расписанию) и ручной (по MQTT-команде).

    > 💡 Подсказка: Используйте узел `cron-plus` для создания гибких расписаний. Например, можно задать разное время активации 'Ночи' для будних и выходных дней в одном узле. Этот узел гораздо мощнее стандартного `Inject`. Если он не установлен, добавьте его через `Manage Palette`.

    Автоматическое включение и выключение по расписанию

    Самый распространенный способ управления режимом «Ночь» — по времени. Например, включать в 23:00 и выключать в 07:00.

  • Установка узла `cron-plus`: Если еще не установлен, зайдите в `☰ -> Manage Palette -> Install`, найдите `node-red-contrib-cron-plus` и установите.
  • Создание триггера "Включить Ночь":
  • * Добавьте узел `cron-plus` на рабочее поле.

    * Настройте его:

    * Name: `CRON: Включить 'Ночь' (23:00)`

    * Schedule: Добавьте новое выражение.

    * Expression Type: `cron`

    Expression: `0 23 ` (это означает "в 0 минут 23 часа, каждый день, каждый месяц, каждый день недели").

    * Topic: `cron-night-on` (для удобства отладки)

    * После узла `cron-plus` поставьте узел `Change`, который установит `msg.payload` в строковое значение `night`.

    * Подключите выход узла `Change` ко входу вашего `subflow-modes`.

  • Создание триггера "Выключить Ночь":
  • * Аналогично создайте второй узел `cron-plus`.

    * Name: `CRON: Выключить 'Ночь' (07:00)`

    Expression: `0 7 `

    * Topic: `cron-night-off`

    * После него поставьте узел `Change`, который установит `msg.payload` в строковое значение `home` (так как после пробуждения мы переходим в режим «Дома»).

    * Подключите его также ко входу `subflow-modes`.

    Теперь система будет автоматически переключаться в ночной режим в 23:00 и возвращаться в дневной в 7:00.

    Ручное управление через MQTT

    Автоматика — это хорошо, но пользователь всегда должен иметь возможность управлять системой вручную. Например, если он решил лечь спать раньше или, наоборот, задержался допоздна. Для этого мы создадим MQTT-интерфейс.

  • Добавьте узел `mqtt in`:
  • * Server: Выберите ваш MQTT-брокер.

    * Topic: `hi/modes/set`

    * QoS: `1`

    * Output: `a parsed JSON object` (это важно, т.к. мы ожидаем JSON).

  • Создайте узел `Function` для разбора команды:
  • Хотя `subflow-modes` ожидает на вход простую строку (`home`, `away`, `night`), хорошей практикой является использование структурированного формата JSON для команд. Это обеспечивает расширяемость в будущем. Наш контракт сообщения будет таким:

        {

    "mode": "night"

    }

    Код в узле `Function` будет извлекать значение и передавать его дальше:

        // Проверяем, что msg.payload является объектом и содержит ключ "mode"

    if (msg.payload && typeof msg.payload === 'object' && msg.payload.hasOwnProperty('mode')) {

    // Извлекаем значение и помещаем его в msg.payload для subflow

    msg.payload = msg.payload.mode;

    return msg;

    } else {

    node.warn("Invalid command format received on hi/modes/set. Expected JSON with 'mode' key.", msg);

    return null;

    }

  • Соберите поток:
  • `[mqtt in: hi/modes/set]` -> `[Function: Parse Command]` -> `[subflow-modes]`

    Теперь вы можете из любого MQTT-клиента (например, мобильного приложения или с настенной панели) отправить сообщение в топик `hi/modes/set` с телом `{"mode": "night"}` или `{"mode": "home"}`, чтобы принудительно сменить режим.

    ---

    Реализация сценария 'Ночное освещение'

    Теперь самое интересное — использование нового режима на практике. Мы создадим сценарий, в котором датчик движения в коридоре будет включать свет на разную яркость в зависимости от того, активен режим «Дома» или «Ночь».

    Для этого нам нужно:

  • Знать текущий режим системы в любой момент времени.
  • Получать события от датчика движения.
  • Отправлять команды на диммирование светильников (в нашем примере — через шлюз MQTT в DALI).
  • Схема потока (Flow)

    [mqtt in: hi/modes/current] --+--> [flow.set('current_mode')]
    

    |

    [mqtt in: sensor/corridor/motion] --> [Function: Night Light Logic] --+--> [mqtt out: hi/dali/corridor/set]

    |

    +--> (no action)

    Шаг 1: Подписка на состояние режима

    Чтобы наш сценарий знал текущий режим, он должен подписаться на топик, в который `subflow-modes` публикует состояние.

  • Создайте узел `mqtt in` с топиком `hi/modes/current`.
  • После него поставьте узел `Change`, который будет записывать полученное значение (`msg.payload`) в переменную контекста потока: `Set` `flow.current_mode` `to` `msg.payload`. Это создаст "локальную копию" глобального состояния.
  • Шаг 2: Разработка логики в зависимости от режима

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

  • Добавьте узел `mqtt in`, подписанный на топик вашего датчика движения, например, `hi/sensors/corridor/motion`. Будем считать, что он присылает `{"motion": true}` при срабатывании.
  • Добавьте узел `Function` с названием `Night Light Logic`. Это "мозг" нашего сценария.
  •     // Получаем сообщение от датчика движения

    const motionDetected = msg.payload.motion;

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

    if (!motionDetected) {

    return null;

    }

    // Получаем текущий режим системы из контекста потока

    const currentMode = flow.get('current_mode') || 'away';

    let command = null;

    // --- Главная логика ---

    switch (currentMode) {

    case 'home':

    // В режиме "Дома" включаем свет на полную яркость

    command = {

    state: "ON",

    brightness: 254 // 254 - это 100% для DALI

    };

    break;

    case 'night':

    // В режиме "Ночь" включаем свет приглушенно

    command = {

    state: "ON",

    brightness: 38 // ~15% яркости для DALI, комфортный ночной уровень

    };

    break;

    case 'away':

    default:

    // Если дома никого нет, свет не включаем.

    // Можно добавить логику для отправки уведомления о движении.

    node.warn("Motion detected in corridor while in 'away' mode!");

    return null;

    }

    // Формируем сообщение для отправки на DALI-шлюз

    msg.payload = command;

    // Доп. логика: выключить свет через 1 минуту

    // Для этого можно использовать узел Trigger после этой функции

    // или отправить команду с параметром 'transition'

    // command.transition = 60; // DALI команда для плавного выключения

    return msg;

  • Отправка команды: Подключите выход узла `Function` к узлу `mqtt out`, который настроен на отправку команд в топик вашего светильника, например, `hi/dali/corridor/set`.
  • Контракт сообщения для DALI-шлюза:

    Мы использовали следующий формат `msg.payload`, который является типичным для управления диммируемыми светильниками через MQTT:

    {
    

    "state": "ON",

    "brightness": 38,

    "transition": 5

    }

    Теперь, если датчик движения сработает днем (в режиме `home`), свет в коридоре плавно включится на 100%. Если же это произойдет ночью (в режиме `night`), свет включится всего на 15%, создавая комфортную подсветку пути и не нарушая сон. В режиме `away` свет не включится вообще, а система может зафиксировать потенциально тревожное событие.

    ---

    Итоги и дальнейшее развитие системы

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

    Краткий обзор проделанной работы:

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

    Что дальше?

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

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