Триггеры для смены режимов: геопозиция, время, ручные кнопки, датчики
Введение в триггеры: От графа состояний к реальным событиям
ведение в триггеры: От графа состояний к реальным событиям
В предыдущих уроках мы спроектировали абстрактную модель умного дома — граф состояний, включающий ключевые режимы «Дома», «Нет дома» и «Ночь». Эта модель является логическим каркасом системы. Теперь наша задача — вдохнуть в нее жизнь, связав эти абстрактные состояния с реальными событиями. Инструментом для такой связи служит триггер.
Триггер — это любое событие, физическое или логическое, которое инициирует переход системы из одного состояния (режима) в другое. Если граф состояний — это карта дорог, то триггеры — это дорожные знаки и указатели, которые направляют движение по этой карте.> 🔗 Связанный материал: Перед изучением триггеров убедитесь, что вы полностью понимаете концепцию и спроектировали базовый граф состояний «Дома», «Нет дома», «Ночь», как это было подробно рассмотрено в уроке COURSE-07-M02-L01 «Проектирование графа состояний».
Все триггеры можно классифицировать по их природе и источнику:
1. Триггеры по времени
Самый простой и предсказуемый тип. Система меняет режим по заранее заданному расписанию или астрономическим событиям.
Пример:* Активация режима «Ночь» каждый день в 23:00. Пример:* Переход в режим «Утро» за 30 минут до рассвета.> 💡 UX-совет: Триггеры по времени предсказуемы, но не гибки. Пользователь может лечь спать позже или проснуться раньше обычного. Важно не использовать время как жесткий безусловный триггер для критичных смен режимов — рекомендуется всегда комбинировать его с проверкой условий (освещенности, движения или статуса других устройств).
2. Ручные триггеры (по действию пользователя)
Пользователь явным образом инициирует смену режима. Это обеспечивает ему полный контроль над системой и возможность ручного переопределения (override) любой автоматики.
Пример:* Нажатие физической кнопки «Ухожу» у входной двери. Пример:* Активация режима «Нет дома» через дашборд мобильного приложения или голосовую команду.> ⚠️ Принцип Override (Переопределение): Ручной триггер всегда должен обладать наивысшим приоритетом. Если логика датчиков предполагает, что в доме никого нет, но пользователь вручную активирует режим «Дома» через интерфейс — система должна подчиниться команде человека и временно заблокировать конфликтующие автоматические переходы.
3. Триггеры по местоположению (геопозиция)
Система автоматически реагирует на появление или исчезновение пользователей в определенной географической зоне (геофенсинг).
Пример:* Режим «Дома» активируется, когда смартфон первого члена семьи входит в 500-метровую зону вокруг коттеджа.> 💡 Особенности проектирования: При настройке гео-триггеров критически важно правильно подобрать радиус зоны геофенсинга. Слишком малый радиус не позволит умному дому своевременно подготовиться к вашему приезду (например, заранее открыть ворота), а слишком большой может спровоцировать ложные срабатывания, если вы просто проезжаете по району по другим делам.
4. Автоматические триггеры от датчиков
Система делает логический вывод о необходимости смены режима на основе анализа потока данных с различных сенсоров (микроволновые датчики присутствия, PIR-датчики движения, герконы на дверях).
Пример:* Если в течение 2 часов в доме не зафиксировано ни одного присутствия, система предполагает, что все ушли, и активирует режим «Нет дома».Ключевым требованием к любому триггеру является его надежность и однозначность. Ложное срабатывание или, наоборот, пропуск события могут привести к нежелательным последствиям: от внезапного включения яркого света посреди ночи до отключения климат-контроля и охраны, когда хозяева отсутствуют. Поэтому проектирование системы триггеров — это не просто разовая настройка разрозненных датчиков, а создание отказоустойчивой, застрахованной от ложных срабатываний и логически выверенной архитектуры обработки событий.
Практика: Триггеры по времени для режима 'Ночь'
рактика: Триггеры по времени для режима 'Ночь'
Самым распространенным и легко реализуемым триггером является временной. Для его создания в среде Node-RED идеально подходит узел `cron-plus`, который обладает значительно большей гибкостью, чем стандартный узел `inject`. Он позволяет задавать сложные расписания, включая динамические (рассвет/закат) и множественные графики в одном узле.
> 💡 Подсказка: Используйте несколько выражений Cron в одной ноде `cron-plus`, чтобы задать разные расписания для будних и выходных дней, не усложняя поток лишними узлами. Например, можно настроить активацию режима «Ночь» в 23:00 по будням и в 01:00 по выходным.
Реализация потока
Поток для временного триггера предельно прост:
`[cron-plus]` -> `[function: Формирование команды]` -> `[mqtt out: Публикация команды]`
Шаг 1: Настройка узла `cron-plus`
Шаг 2: Настройка узла `function`
Этот узел отвечает за формирование сообщения, соответствующего нашему контракту сообщений, для отправки в систему управления режимами.
// Имя узла: Формирование команды "Ночь"
// Получаем тему из входящего сообщения, если она есть
const triggerSource = msg.topic || "trigger:time_cron_night";
// Формируем payload в соответствии со стандартом
// Это не команда на прямое исполнение, а ЗАПРОС на смену режима.
// Логика приоритетов будет обрабатывать его позже.
msg.payload = {
"request": "set_mode",
"value": "Night",
"source": {
"type": "time",
"id": triggerSource
},
// Приоритет временного триггера - низкий. Например, 10.
"priority": 10,
"ts": Date.now()
};
// Устанавливаем топик для отправки запросов на смену режима
msg.topic = "hi/system/mode/request";
node.status({fill:"blue", shape:"dot", text:"Night mode requested at " + new Date().toLocaleTimeString()});
return msg;
// Это сообщение будет отправлено в центральный обработчик режимов
> 💡 UX/Override: Обратите внимание на `"priority": 10` в коде. Временные триггеры обычно имеют самый низкий приоритет. Это сделано для того, чтобы автоматика по расписанию не перебивала ручные режимы. Например, если пользователь заблокировал дом с высоким приоритетом или запустил режим «Вечеринка» с приоритетом `50`, триггер на `23:00` сгенерирует заявку на режим «Ночь», но система ее отклонит, сохранив комфортный для пользователя UX (подробнее про маршрутизацию и обработку таких конфликтов мы говорили в уроке по графу состояний).
Шаг 3: Настройка узла `mqtt out`
Теперь каждый день в 23:00 система будет генерировать стандартизированный запрос на активацию режима «Ночь» и отправлять его на обработку.
Чек-лист и план тестирования временного триггера
Прежде чем оставлять триггер работать в боевом режиме, убедитесь в правильности формирования контракта и отработки сценариев:
- [ ] Изменение времени (Positive test): Временно измените Cron-выражение в `cron-plus`, чтобы оно срабатывало через 1–2 минуты от текущего времени (например, если сейчас 14:05, поставьте `7 14 *`).
- [ ] Подписка на брокер: Включите MQTT-клиент (например, MQTT Explorer или ноду `mqtt in` + `debug` в Node-RED), подписанный на топик `hi/system/mode/request`.
- [ ] Проверка контракта: Дождитесь срабатывания таймера. Убедитесь, что в топике появилось сообщение с запросом `set_mode`, верным целевым значением `value: "Night"`, приоритетом `10` и корректным источником `type: "time"`.
- [ ] Статус узла: Убедитесь, что под нодой `function` появился синий маркер с текстом: "Night mode requested at [время срабатывания]".
- [ ] Проверка переопределения (UX/Override тест): Сымитируйте через отдельную ноду `inject` активный режим в системе с приоритетом `100`. Дождитесь срабатывания таймера `cron-plus` и убедитесь, что центральный обработчик получил запрос на "Ночь", но отклонил его из-за низкого приоритета (система должна остаться в прежнем состоянии).
- [ ] Откат: Верните правильное боевое значение Cron `0 23 *` и сделайте Deploy рабочего процесса.
Триггеры по геопозиции: Реализация надежного детектора присутствия
риггеры по геопозиции: Реализация надежного детектора присутствия
Определение присутствия пользователей — одна из самых востребованных, но и самых сложных задач автоматизации. Ее решение позволяет реализовать ключевые режимы «Дома» и «Нет дома» (подробнее правила и ограничения этих переходов разобраны в уроке по логике графа состояний).
Существует несколько методов отслеживания местоположения:
- Мобильные приложения: Специализированные приложения, такие как OwnTracks, GPSLogger, Home Assistant Companion, отправляют данные о местоположении устройства на сервер (в нашем случае, на MQTT-брокер контроллера HI). Это наиболее точный и гибкий метод.
- Сетевые сервисы: Проверка присутствия устройств в локальной Wi-Fi сети или по статическому IP-адресу (с помощью узла `ping`). Менее надежен из-за режимов сна на мобильных устройствах, но хорош как вспомогательный триггер.
- GPS-трекеры: Аппаратные трекеры в автомобилях или личные трекеры.
Мы сосредоточимся на OwnTracks, так как он нативно работает через MQTT и легко интегрируется с нашей платформой.
Логика «Первого пришедшего / последнего ушедшего»
Когда в доме живет несколько человек, простого сигнала «пользователь вошел/вышел из зоны» недостаточно. Режим «Нет дома» должен активироваться только тогда, когда последний пользователь покинул дом. А режим «Дома» — когда первый пользователь вернулся.
Эту логику агрегации состояний необходимо реализовать в Node-RED. Для хранения статуса присутствия каждого пользователя мы будем использовать контекст потока (flow context), который обеспечивает сохранение данных между вызовами.
Поток для обработки данных OwnTracks:`[mqtt in: owntracks/+/+]` -> `[function: Агрегатор присутствия]` -> `[mqtt out: Публикация команды]`
Код для узла `function: Агрегатор присутствия`:/*
Имя узла: Агрегатор присутствия (OwnTracks)
Этот узел слушает топик owntracks/user/device и обрабатывает события входа/выхода
из геозоны 'home'.
Входящее сообщение от OwnTracks (msg.payload - JSON):
{ "_type": "transition", "event": "enter", "lat": 55.75, "lon": 37.61, "tid": "u1", "tst": 1678886400, "desc": "home" }
{ "_type": "transition", "event": "leave", "lat": 55.75, "lon": 37.61, "tid": "u2", "tst": 1678886500, "desc": "home" }
*/
const data = msg.payload;
// 1. Валидация входящих данных
if (data._type !== 'transition' || data.desc !== 'home') {
// Это не событие пересечения геозоны 'home', игнорируем
return null;
}
const userId = msg.topic.split('/')[1]; // Извлекаем имя пользователя из топика owntracks/user/device
const eventType = data.event; // 'enter' или 'leave'
// 2. Получаем или инициализируем список пользователей в контексте
let usersAtHome = flow.get('usersAtHome') || [];
let previousUserCount = usersAtHome.length;
// 3. Обновляем статус пользователя
if (eventType === 'enter' && !usersAtHome.includes(userId)) {
// Пользователь вошел, добавляем в список, если его там нет
usersAtHome.push(userId);
node.log(`Пользователь ${userId} пришел домой. Сейчас дома: ${usersAtHome.join(', ')}`);
} else if (eventType === 'leave') {
// Пользователь ушел, удаляем из списка
usersAtHome = usersAtHome.filter(user => user !== userId);
node.log(`Пользователь ${userId} ушел из дома. Сейчас дома: ${usersAtHome.join(', ')}`);
}
// 4. Сохраняем обновленный список в контекст
flow.set('usersAtHome', usersAtHome);
let currentUserCount = usersAtHome.length;
let newMode = null;
// 5. Логика "первый пришел / последний ушел"
if (previousUserCount === 0 && currentUserCount > 0) {
// Пришел первый человек
newMode = "Home";
} else if (previousUserCount > 0 && currentUserCount === 0) {
// Ушел последний человек
newMode = "Away";
}
// 6. Если режим изменился, формируем и отправляем запрос
if (newMode) {
msg.payload = {
"request": "set_mode",
"value": newMode,
"source": {
"type": "geo",
"id": `owntracks:${userId}:${eventType}`
},
// Приоритет гео-триггера выше, чем у временного. Например, 50.
"priority": 50,
"ts": Date.now()
};
msg.topic = "hi/system/mode/request";
node.status({fill:"green", shape:"dot", text:`${newMode} requested. Users: ${currentUserCount}`});
return msg;
}
// Если режим не изменился, ничего не отправляем
node.status({fill:"grey", shape:"ring", text:`No change. Users: ${currentUserCount}`});
return null;
Этот поток обеспечивает надежное переключение между режимами «Дома» и «Нет дома» для любого количества пользователей.
Защита от ложных срабатываний (GPS Jitter) и UX
Одной из главных проблем GPS-трекинга является «дрейф» координат. Смартфон пользователя может временно потерять сигнал (например, в лифте, глубоком подвале или из-за банальной погрешности чипа) и отправить ошибочное событие выхода из зоны.
Чтобы избежать неприятного UX, когда система внезапно выключает свет и музыку, считая, что все ушли, рекомендуется использовать следующие практики:
Чек-лист тестирования гео-агрегатора
Перед вводом автоматизации в эксплуатацию прогоните следующие тестовые сценарии через отправку inject-узлом имитационных пакетов:
- [ ] Сценарий прибытия: В контексте пусто (`usersAtHome = []`). Имитируется `enter`. Ожидается отправка команды `Home` с нужным приоритетом.
- [ ] Добавление пользователей: Имитируется `enter` для другого пользователя. Контекст обновился (теперь в массиве двое), но команда `Home` повторно не отправлена (чтобы не нарушить текущие ручные изменения сценария светом).
- [ ] Частичный уход: Имитируется `leave` для одного из пользователей. Контекст обновился (остался один), команда `Away` не отправлена.
- [ ] Уход последнего: Имитируется `leave` для последнего оставшегося пользователя. Контекст пуст. Отправлена команда `Away`.
- [ ] Дублирование сигналов: Дважды подряд отправляется `enter` для одного и того же пользователя. Поток корректно обрабатывает данные и не дублирует имя в массиве присутствующих.
- [ ] Проверка анти-джиттера (Debounce): Имитируется `leave` для единственного пользователя и в течение заданного окна (например, 2 минут) снова подается `enter`. Система отменяет таймер и не отправляет команду `Away`.
- [ ] Блокировка при гостях (Override): При активном режиме «Гости» имитируется `leave` последнего системного пользователя. Платформа должна отказать в понижении режима до `Away`, отдавая приоритет защитному флагу или ручному управлению.
Интеграция ручных триггеров: кнопки KNX, Modbus и веб-интерфейсы
нтеграция ручных триггеров: кнопки KNX, Modbus и веб-интерфейсы
Несмотря на всю мощь автоматизации, у пользователя всегда должна оставаться возможность прямого и безусловного управления системой. Ручные триггеры имеют наивысший приоритет.
> ⚠️ Внимание: Всегда предусматривайте ручные триггеры для смены ключевых режимов! В случае отказа автоматики (например, сбой GPS на телефоне или разрядившийся аккумулятор), у пользователя должен быть надежный и простой способ управления системой.
UX и концепция Override (Ручное переопределение)
С точки зрения пользовательского опыта (UX), ручное действие — это абсолютный приказ. Если автоматическая подсистема (например, расписание датчика освещенности) считает, что должен активироваться режим «Ночь», но пользователь вручную выбирает «Дома» или «Вечеринка», система обязана подчиниться человеку. Этот механизм называется Override (ручное переопределение).
Чтобы Override работал корректно:
Рассмотрим реализацию ручных триггеров на примере физических кнопок и виртуальных интерфейсов.
Выключатели и панели KNX
Стеновые выключатели KNX — это умные устройства, которые самостоятельно формируют и отправляют события (телеграммы) в шину. Интеграция таких триггеров в общую логику Node-RED (через IP-шлюз или программный мост, такой как `knxd`) позволяет использовать их для смены глобальных режимов.
Физические кнопки (Modbus)
Предположим, у входа в дом установлена панель со стандартными "сухими" контактами (кнопками без фиксации), подключенная к модулю дискретных входов, который опрашивается по шине RS-485 Modbus. Мы можем реализовать логику, при которой короткое нажатие выключает свет в прихожей, а длинное (удержание более 2 секунд) активирует общую команду или режим «Нет дома».
> 💡 Чек-лист тестирования аппаратных кнопок:
> - [ ] Настроен ли антидребезг (debounce) контактов аппаратно в модуле дискретных входов или программно через узлы Node-RED?
> - [ ] Корректно ли работает система при кратковременной потере связи с Modbus/KNX-шлюзом (исключены ли фантомные нажатия)?
> - [ ] Если предусмотрено длительное нажатие (Hold), установлен ли программный лимит (таймаут), чтобы кнопка не зависла в активном состоянии при поломке механической пружины?
> - [ ] Работает ли LED-подтверждение (feedback) на физической панели после успешного применения нового режима системой?
Виртуальные кнопки (MQTT)
Самый гибкий способ — создать кнопки в любом пользовательском интерфейсе (Node-RED Dashboard, Home Assistant, мобильное приложение) и настроить их на отправку команд по MQTT.
Поток обработки команд из UI:`[mqtt in: hi/ui/commands]` -> `[json]` -> `[switch: по msg.payload.command]` -> `[function: Формирование команды]`
Шаг 1: Настройка `mqtt in`- Подпишитесь на топик, куда ваш UI отправляет команды, например, `hi/ui/commands`.
- Узел будет маршрутизировать сообщения в зависимости от поступающей команды.
- Настройте проверку свойства `msg.payload.command`. Например, если значение равно `set_mode`, сообщение пойдет дальше.
Этот узел получит команду от UI, проверит ее валидность и преобразует в наш стандартный формат запроса. Важно убедиться, что запрашиваемый режим действительно может быть активирован в вашей системе (подробнее о проектировании состояний и переходов между ними мы говорили в уроке по графам состояний).
Пример входящего JSON от кнопки в UI:{
"command": "set_mode",
"target_mode": "Away",
"user": "admin"
}
Код узла `function`:
// Имя узла: Обработка ручной команды
const command = msg.payload;
// Проверяем, что это команда на смену режима
if (command.command !== 'set_mode' || !command.target_mode) {
node.warn("Некорректная ручная команда:", command);
return null;
}
const validModes = ["Home", "Away", "Night"];
if (!validModes.includes(command.target_mode)) {
node.error(`Попытка установить невалидный режим: ${command.target_mode}`, msg);
return null;
}
// Формируем стандартный запрос
msg.payload = {
"request": "set_mode",
"value": command.target_mode,
"source": {
"type": "manual",
"id": `ui:${command.user || 'unknown'}`
},
// Ручной триггер (Override) всегда имеет наивысший приоритет. Например, 100.
"priority": 100,
"ts": Date.now()
};
msg.topic = "hi/system/mode/request";
node.status({ fill:"yellow", shape:"dot", text:`${command.target_mode} by ${command.user}`});
return msg;
Этот подход унифицирует обработку команд от любых возможных источников ручного управления, создавая надежный и предсказуемый канал взаимодействия пользователя со своим умным домом.
Автоматические триггеры от датчиков: движение, открытие дверей
втоматические триггеры от датчиков: движение, открытие дверей
Датчики движения (PIR), открытия дверей (герконы) и другие сенсоры могут служить косвенными индикаторами присутствия. Они редко используются для прямого переключения в режим «Дома» (датчик не знает, кто вошел), но очень эффективны для определения отсутствия активности.
Логика «Отсутствия активности»
Идея проста: если мы находимся в режиме «Дома», но в течение длительного времени (например, 2 часов) в доме не было зафиксировано ни одного движения и ни одна дверь не открывалась, высока вероятность, что все ушли, а гео-триггер по какой-то причине не сработал. В этом случае система может подстраховаться и переключится в режим «Нет дома».
> 💡 Архитектурная связь: О том, как именно состояния могут безопасно переходить друг в друга (и почему важно проверять текущий режим перед отправкой команды), подробно разбирается в уроке по графу состояний.
Для реализации этой логики идеально подходит узел `trigger` в Node-RED.
Поток для отслеживания отсутствия активности:`[mqtt in: hi/telemetry/+/motion]` -> `[trigger]` -> `[function: Проверка условий и отправка]`
Шаг 1: Сбор данных с датчиков- Подпишите узел `mqtt in` на все топики с датчиков движения. Используйте wildcard: `hi/telemetry/+/motion`. Мы предполагаем, что датчики отправляют `{"value": true}` при детекции.
Это сердце нашей логики.
Это означает, что при каждом новом сообщении о движении таймер на 2 часа будет сбрасываться и начинать отсчет заново. Только если в течение 2 часов не придет ни одного сообщения, узел `trigger` отправит сообщение дальше по потоку.
Шаг 3: Код узла `function`// Имя узла: Проверка и активация "Away" по неактивности
// 1. Получаем текущий глобальный режим.
// Мы читаем официальный статус, а не запрашиваем его.
// Предполагается, что он хранится в глобальном контексте.
const currentMode = global.get("systemMode") || "Unknown";
// 2. Проверяем условия
// Триггер сработает, только если сейчас режим "Дома".
// Это предотвращает ложные срабатывания ночью или когда мы и так в "Нет дома".
if (currentMode === "Home") {
// 3. Формируем запрос на смену режима
msg.payload = {
"request": "set_mode",
"value": "Away",
"source": {
"type": "auto_sensor",
"id": "inactivity_trigger"
},
// Приоритет этого триггера ниже, чем у геопозиции, но выше, чем у времени.
// Он служит страховкой для гео-триггера. Например, 30.
"priority": 30,
"ts": Date.now()
};
msg.topic = "hi/system/mode/request";
node.status({ fill:"orange", shape:"dot", text:"Away requested by inactivity"});
return msg;
}
// Если условия не выполнены, ничего не делаем.
node.status({ fill:"grey", shape:"ring", text:`Inactive, but mode is ${currentMode}`});
return null;
Такой триггер значительно повышает надежность системы, выступая в роли "второй линии обороны" для детектора присутствия.
UX и ручное вмешательство (Override)
Полагаясь исключительно на датчики движения (PIR), вы можете столкнуться с проблемой «спящего человека» — вы заснули на диване или читаете книгу без резких движений, а система решает, что в доме никого нет, и выключает свет.
- Датчики присутствия (mmWave): Для жилых комнат рекомендуется дополнять классические PIR-датчики радарами миллиметрового диапазона (mmWave). Они улавливают микро-движения (включая дыхание), не позволяя таймеру неактивности обнулиться.
- Остановка логики вручную: Если пользователь отменяет режим «Нет дома» (например, через интерфейс умного дома, находясь внутри), системе следует сбросить таймер неактивности с помощью отправки сообщения с payload `{"reset": true}` на скрытый вход узла `trigger`.
Чек-лист тестирования (Test Plan)
Ожидать 2 часа для проверки автоматики неэффективно. При настройке обязательно проведите стресс-тест таймера на коротких промежутках времени:
- [ ] Окно отладки: Временно измените таймаут в узле `trigger` (например, на `15 seconds`).
- [ ] Сброс таймера (Extend delay): Вызовите движение ➔ подождите `10 секунд` ➔ вызовите движение еще раз ➔ подождите `10 секунд`. Убедитесь, что сигнал `Away` не отправлен, так как второй сигнал отложил срабатывание.
- [ ] Срабатывание по истечении: Дождитесь полных `15 секунд` без движений. Убедитесь, что система отправила запрос на режим `Away`.
- [ ] Блокировка по текущему состоянию: Принудительно установите режим `Night` (Сон) для дома. Дождитесь истечения `15 секунд` неактивности. Убедитесь, что код узла `function` заблокировал переход, а под узлом появился статус `Inactive, but mode is Night`.
- [ ] Восстановление: Верните задержку к рабочему значению (`2 hours` или оптимальному под ваши условия) после завершения тестов.
Реализация конечного автомата: Менеджер Режимов
еализация конечного автомата: Менеджер Режимов
Как мы подробно спроектировали в уроке COURSE-07-M02-L01 «Архитектура конечного автомата: управление режимами и приоритеты», для разрешения конфликтов между триггерами используется централизованный конечный автомат. Его задача — слушать все запросы от триггеров и принимать решение о смене режима на основе их приоритетов. Теперь реализуем его в Node-RED.
Архитектура Менеджера Режимов
// Имя узла: Mode Manager - Priority Logic
const request = msg.payload; // Входящий запрос от hi/system/mode/request
// Получаем текущее состояние и приоритет его источника
let currentMode = global.get('systemMode', 'persistent') || 'Unknown';
let currentPriority = global.get('systemModeSourcePriority', 'persistent') || 0;
// Логика принятия решения
if (request.priority >= currentPriority) {
// Приоритет нового запроса достаточен
// Если новый режим совпадает с текущим, просто обновляем источник и приоритет
if (request.value === currentMode) {
node.log(`Режим ${currentMode} подтвержден новым источником с приоритетом ${request.priority}`);
} else {
node.log(`Смена режима: ${currentMode} (p:${currentPriority}) -> ${request.value} (p:${request.priority})`);
}
// Сохраняем новое состояние и приоритет в глобальный персистентный контекст
global.set('systemMode', request.value, 'persistent');
global.set('systemModeSourcePriority', request.priority, 'persistent');
// Формируем сообщение для отправки в "статусный" топик
msg.payload = {
"mode": request.value,
"source": request.source,
"priority": request.priority,
"previous_mode": currentMode,
"ts": Date.now()
};
msg.topic = "hi/system/mode/state";
node.status({fill:"green", shape:"dot", text:`New mode: ${request.value} @ p:${request.priority}`});
return msg; // Отправляем сообщение для всех подписчиков
} else {
// Приоритет нового запроса ниже, он игнорируется
node.log(`Запрос на смену режима на ${request.value} (p:${request.priority}) отклонен. Текущий режим ${currentMode} имеет более высокий приоритет (p:${currentPriority})`);
node.status({fill:"grey", shape:"ring", text:`Ignored req for ${request.value}`});
return null;
}
Такая архитектура с разделением на генерацию запросов и централизованное принятие решений делает систему предсказуемой, масштабируемой и легкой в отладке.
Обработка ручных переопределений (Overrides)
Использование приоритетов элегантно решает проблему ручного вмешательства:
- Автоматика: Если система переходит в режим «Нет дома» по геопозиции (приоритет `50`), а затем датчик движения внутри дома фиксирует активность (приоритет `20`), он не сможет переключить систему обратно, так как `20 < 50`. Сработает сигнализация (привязанная к режиму).
- Ручное управление (Override): Вы нажимаете физическую кнопку на входе или в дашборде (приоритет `100`), запрашивая режим «Дома». Менеджер сравнивает `100 >= 50`, авторизует запрос, переводит дом в режим «Дома» и снимает сигнализацию.
- Сброс приоритета (UX): Чтобы дом не «застрял» в ручном режиме с приоритетом `100` (что заблокирует любую автоматику), важно использовать «Таймеры сброса» при ручных нажатиях или привязывать обнуление приоритета к системным событиям (например, утреннее пробуждение по расписанию принудительно устанавливает режим «Утро» с базовым приоритетом `10`).
Чек-лист и план тестирования
Для проверки логики Менеджера Режимов проведите следующие тесты:
- [ ] Тест повышения приоритета: Установите режим запросом с приоритетом `20` (например, датчик освещенности). Затем отправьте запрос на другой режим с приоритетом `50` (геозона). Убедитесь, что `global.systemMode` изменился и топик `hi/system/mode/state` обновился.
- [ ] Тест блокировки (Override): Установите режим ручной кнопкой с приоритетом `100`. После этого имитируйте срабатывание сенсора с приоритетом `20`. Убедитесь, что менеджер проигнорировал сенсор (для этого можно вывести лог в консоль в блоке `else` или ориентироваться на серый статус узла).
- [ ] Тест обновления источника (Тот же режим): Отправьте запрос на тот же режим, но с более высоким приоритетом. Убедитесь, что приоритет обновился в глобальном контексте, но логика дома не стала запускать полные циклы инициализации освещения или климата повторно (смена состояния не произошла, обновились лишь метаданные источника).
- [ ] Тест перезагрузки (Persistence): Нажав `Deploy` или перезапустив Docker-контейнер Node-RED, убедитесь, что персистентный контекст корректно восстанавливает текущий режим и его приоритет, избегая сброса дома на статус «Unknown» сразу после запуска.
Что дальше
В этом уроке мы научились создавать триггеры для смены режимов и, что более важно, построили надежную архитектуру для управления ими на основе приоритетов. В следующем уроке мы перейдем к практической реализации сценариев, которые будут выполняться при смене этих глобальных режимов: что именно должен делать дом, когда он переходит в состояние «Ночь» или «Нет дома».