Практика: Добавление режима 'Ночь' и управление ночным освещением
Введение: Расширение графа состояний режимом 'Ночь'
Ранее в курсе мы спроектировали и реализовали базовую, но крайне важную систему управления режимами «Дома» и «Нет дома». Эта система является ядром для большинства сценариев автоматизации, позволяя контроллеру понимать глобальный контекст происходящего на объекте. Она определяет, нужно ли экономить энергию, активировать охранные функции или обеспечивать максимальный комфорт для присутствующих людей.
Однако бинарной логики "кто-то есть / никого нет" часто бывает недостаточно для создания по-настоящему интеллектуальной и комфортной среды. День и ночь внутри дома — это два совершенно разных мира с разными требованиями к освещению, климату и безопасности. Именно для этого вводится третье ключевое состояние — режим «Ночь».
Необходимость режима «Ночь» продиктована тремя основными факторами:
Цель этого урока — расширить наш существующий конечный автомат (FSM), добавив в него состояние «Ночь». Мы последовательно выполним следующие шаги:
- Модифицируем Subflow для управления режимами, который мы создали ранее, чтобы он корректно обрабатывал новое состояние.
- Создадим триггеры для автоматической и ручной активации/деактивации режима «Ночь».
- Реализуем практический сценарий ночного дежурного освещения, который будет работать только в соответствующем режиме.
В результате вы научитесь не просто добавлять новые состояния в систему, но и создавать сложную, зависимую от контекста логику, которая делает автоматизацию по-настоящему "умной".
---
Модификация Subflow для поддержки режима 'Ночь'
одификация Subflow для поддержки режима 'Ночь'
Основой нашей системы управления режимами является переиспользуемый компонент — Subflow, который мы назвали `subflow-modes`. Этот "черный ящик" отвечает за валидацию команд, смену состояний и публикацию текущего режима для всей системы. Сейчас мы внесем в него изменения, чтобы добавить поддержку режима «Ночь».
> ⚠️ Внимание: Перед внесением изменений в рабочий subflow настоятельно рекомендуется создать его резервную копию. Экспортируйте текущую версию subflow в JSON-файл через меню Node-RED (☰ -> Export -> Selected Nodes). Это позволит быстро вернуться к рабочей версии в случае ошибки.
Пошаговое редактирование Subflow
Ключевое логическое правило: в режим «Ночь» можно перейти только из режима «Дома». Нелогично активировать ночной режим, если дома никого нет. Аналогично, выход из режима «Ночь» должен переводить систему обратно в «Дома».
Обновление кода в узле 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;
Ключевые изменения в коде:
- Валидация (`allowedModes`): Мы добавили `'night'` в массив разрешенных режимов. Теперь команда на установку этого режима не будет вызывать ошибку.
- Логика FSM: Добавлено новое условие: `if (requestedMode === 'night' && currentMode !== 'home')`. Эта строка — сердце нового правила. Она проверяет, что если кто-то пытается включить «Ночь», когда система находится в режиме «Нет дома» (`away`), команда будет проигнорирована. Это предотвращает нелогичные состояния.
- Сохранение состояния: Мы продолжаем использовать персистентный контекст (`flow.set('current_mode', ..., 'file')`), что гарантирует восстановление режима после перезагрузки контроллера.
После сохранения кода закройте редактор subflow и не забудьте нажать кнопку Deploy.
Тестирование Subflow
Для проверки создайте временный тестовый поток:
Проведите тесты:
- Нажмите `Inject` с `home`. В Debug должно появиться `home`.
- Нажмите `Inject` с `night`. В Debug должно появиться `night`.
- Нажмите `Inject` с `away`. В Debug должно появиться `away`.
- Важный тест: Снова нажмите `Inject` с `night`. На этот раз в `Debug` ничего не должно появиться, а в логах (и под узлом Function внутри subflow) вы увидите предупреждение `Cannot switch to 'night' mode from 'away' mode`.
Это подтверждает, что ваша логика конечного автомата работает корректно.
Триггеры для смены режима: по времени и вручную
Теперь, когда наш `subflow-modes` готов обрабатывать новый режим, нам нужны "пульты управления" — триггеры, которые будут отправлять ему команды. Мы создадим два основных типа триггеров: автоматический (по расписанию) и ручной (по MQTT-команде).
> 💡 Подсказка: Используйте узел `cron-plus` для создания гибких расписаний. Например, можно задать разное время активации 'Ночи' для будних и выходных дней в одном узле. Этот узел гораздо мощнее стандартного `Inject`. Если он не установлен, добавьте его через `Manage Palette`.
Автоматическое включение и выключение по расписанию
Самый распространенный способ управления режимом «Ночь» — по времени. Например, включать в 23:00 и выключать в 07:00.
* Добавьте узел `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-интерфейс.
* Server: Выберите ваш MQTT-брокер.
* Topic: `hi/modes/set`
* QoS: `1`
* Output: `a parsed JSON object` (это важно, т.к. мы ожидаем JSON).
Хотя `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"}`, чтобы принудительно сменить режим.
---
Реализация сценария 'Ночное освещение'
Теперь самое интересное — использование нового режима на практике. Мы создадим сценарий, в котором датчик движения в коридоре будет включать свет на разную яркость в зависимости от того, активен режим «Дома» или «Ночь».
Для этого нам нужно:
Схема потока (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` публикует состояние.
Шаг 2: Разработка логики в зависимости от режима
Теперь создаем основной поток, который реагирует на датчик движения.
// Получаем сообщение от датчика движения
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;
Мы использовали следующий формат `msg.payload`, который является типичным для управления диммируемыми светильниками через MQTT:
{
"state": "ON",
"brightness": 38,
"transition": 5
}
- `state`: Состояние (`ON`/`OFF`).
- `brightness`: Уровень яркости (часто от 0 до 254 или 0-100).
- `transition` (опционально): Время в секундах для плавного изменения яркости.
Теперь, если датчик движения сработает днем (в режиме `home`), свет в коридоре плавно включится на 100%. Если же это произойдет ночью (в режиме `night`), свет включится всего на 15%, создавая комфортную подсветку пути и не нарушая сон. В режиме `away` свет не включится вообще, а система может зафиксировать потенциально тревожное событие.
---
Итоги и дальнейшее развитие системы
В рамках этого урока мы проделали значительную работу по усовершенствованию нашей системы автоматизации, превратив ее из простой бинарной модели в более гибкую и контекстно-зависимую.
Краткий обзор проделанной работы:- Мы успешно расширили граф состояний нашей системы, добавив критически важный режим «Ночь».
- Модифицировали основной управляющий Subflow, внедрив в него логику конечного автомата, которая корректно обрабатывает переходы между тремя состояниями: `home`, `away` и `night`.
- Создали надежные триггеры для смены режимов, комбинируя автоматизацию по расписанию (`cron-plus`) и возможность ручного управления через MQTT.
- На практическом примере сценария ночного освещения мы увидели, как глобальные режимы позволяют создавать элегантные и эффективные решения, где одно и то же событие (срабатывание датчика движения) приводит к разным результатам в зависимости от времени суток.
Эта работа закладывает прочный фундамент для дальнейшего наращивания функциональности. Вы освоили не просто конкретную задачу, а методологию расширения системы новыми состояниями, что является ключевым навыком для архитектора автоматизации.
Что дальше?Текущая система режимов уже очень функциональна, но есть пути для ее развития. В следующих уроках мы рассмотрим более сложные концепции:
- Приоритеты режимов: Что произойдет, если сработает датчик протечки (аварийный режим), когда активен режим «Ночь»? Мы введем систему приоритетов, где аварийные режимы («Пожар», «Протечка») всегда будут иметь высший ранг и переопределять любые другие.
- Сложные режимы: Мы научимся создавать и управлять более комплексными режимами, такими как «Отпуск» (который включает имитацию присутствия), «Гости» (который меняет правила доступа и отключает некоторые сценарии) или «Уборка».
Это позволит строить еще более интеллектуальные, безопасные и адаптируемые системы автоматизации на платформе HI.