ГлавнаяАкадемияСценарии умного дома: режимы, состояния, приоритеты → Триггеры для смены режимов: геопозиция, время, ручные кнопки, датчики

Триггеры для смены режимов: геопозиция, время, ручные кнопки, датчики

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

Введение в триггеры: От графа состояний к реальным событиям

ведение в триггеры: От графа состояний к реальным событиям

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

Триггер — это любое событие, физическое или логическое, которое инициирует переход системы из одного состояния (режима) в другое. Если граф состояний — это карта дорог, то триггеры — это дорожные знаки и указатели, которые направляют движение по этой карте.

> 🔗 Связанный материал: Перед изучением триггеров убедитесь, что вы полностью понимаете концепцию и спроектировали базовый граф состояний «Дома», «Нет дома», «Ночь», как это было подробно рассмотрено в уроке 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`

  • Добавьте узел `cron-plus` на рабочую область.
  • В настройках узла создайте новое расписание (schedule).
  • В поле `Expression` введите Cron-выражение. Для срабатывания каждый день в 23:00 выражение будет `0 23 *`.
  • В поле `Topic` можно указать название события, например, `trigger:night_mode`. Это поможет в дальнейшей отладке.
  • Для динамического расписания (например, активация сценария "Вечер" за 15 минут до заката) можно использовать встроенные возможности `cron-plus`. Выберите тип выражения `solar event` и настройте смещение относительно заката (`sunset`).
  • Шаг 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`

  • Настройте узел на подключение к вашему MQTT-брокеру на контроллере HI.
  • Поле `Topic` оставьте пустым — узел будет использовать `msg.topic`, который мы задали в узле `function` (`hi/system/mode/request`).
  • Установите `QoS` (Quality of Service) в значение `1` или `2` для гарантированной доставки сообщения. Потеря запроса на смену режима может нарушить алгоритм автоматизации.
  • Теперь каждый день в 23:00 система будет генерировать стандартизированный запрос на активацию режима «Ночь» и отправлять его на обработку.

    Чек-лист и план тестирования временного триггера

    Прежде чем оставлять триггер работать в боевом режиме, убедитесь в правильности формирования контракта и отработки сценариев:

    Триггеры по геопозиции: Реализация надежного детектора присутствия

    риггеры по геопозиции: Реализация надежного детектора присутствия

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

    Существует несколько методов отслеживания местоположения:

    Мы сосредоточимся на 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, когда система внезапно выключает свет и музыку, считая, что все ушли, рекомендуется использовать следующие практики:

  • Задержка отработки выхода (Debounce): При получении события `leave` не меняйте режим мгновенно. Вместо этого запускайте таймер (в Node-RED для этого отлично подходит узел `trigger` или `stoptimer`) на 3–5 минут. Если за это время приходит повторное событие `enter`, таймер отменяется, и система понимает, что это был сбой координат.
  • Мультисенсорная валидация: Используйте присутствие устройства в Wi-Fi сети как дополнительный фактор. Запрос на переход в «Away» отправляется только тогда, когда и GPS-зона покинута, и телефон пропал из локальной сети.
  • Персистентный контекст: Настройте Node-RED на сохранение контекста в файл (значение `localfilesystem` в конфигурации). Если контроллер перезагрузится, переменная `usersAtHome` будет восстановлена, и система не «забудет», кто сейчас находится дома.
  • Учет гостей (Ручной Override): Если дома остались гости (у которых нет трекера на смартфоне), а последний жилец ушел, автоматический переход в «Away» некорректно обесточит дом. Чтобы этого избежать, система должна предусматривать специальный режим «Гости» или блокировать понижение режима, если был задействован ручной override (например, гость включил свет с физического выключателя или нажал кнопку «Не выключать» на панели после ухода хозяев).
  • Чек-лист тестирования гео-агрегатора

    Перед вводом автоматизации в эксплуатацию прогоните следующие тестовые сценарии через отправку inject-узлом имитационных пакетов:

    Интеграция ручных триггеров: кнопки KNX, Modbus и веб-интерфейсы

    нтеграция ручных триггеров: кнопки KNX, Modbus и веб-интерфейсы

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

    > ⚠️ Внимание: Всегда предусматривайте ручные триггеры для смены ключевых режимов! В случае отказа автоматики (например, сбой GPS на телефоне или разрядившийся аккумулятор), у пользователя должен быть надежный и простой способ управления системой.

    UX и концепция Override (Ручное переопределение)

    С точки зрения пользовательского опыта (UX), ручное действие — это абсолютный приказ. Если автоматическая подсистема (например, расписание датчика освещенности) считает, что должен активироваться режим «Ночь», но пользователь вручную выбирает «Дома» или «Вечеринка», система обязана подчиниться человеку. Этот механизм называется Override (ручное переопределение).

    Чтобы Override работал корректно:

  • Ручным командам всегда назначается максимальный приоритет (например, `100`), который временно блокирует или подавляет автоматические сценарии с низким приоритетом.
  • Любое ручное переключение должно сопровождаться визуальным или тактильным подтверждением (feedback) — изменение статуса в веб-интерфейсе, подсветка LED-индикатора на стене, либо звуковой сигнал зуммера.
  • Обязательно должен быть предусмотрен механизм снятия Override — автоматически (по истечении заданного таймаута, при наступлении утра) или вручную (снятие галочки/отжатие кнопки в UI).
  • Рассмотрим реализацию ручных триггеров на примере физических кнопок и виртуальных интерфейсов.

    Выключатели и панели KNX

    Стеновые выключатели KNX — это умные устройства, которые самостоятельно формируют и отправляют события (телеграммы) в шину. Интеграция таких триггеров в общую логику Node-RED (через IP-шлюз или программный мост, такой как `knxd`) позволяет использовать их для смены глобальных режимов.

  • Получение телеграммы: Узел `knx in` слушает групповой адрес, назначенный на определенную клавишу (например, `1/1/10` для режима "Уход").
  • Аппаратная обработка событий: В отличие от "глупых" кнопок, панели KNX могут аппаратно распознавать короткие и длинные нажатия, отправляя разные значения DPT (Data Point Type). Задержки, антидребезг и двойные клики настраиваются на уровне ETS, поэтому в Node-RED приходит уже очищенная, готовая к исполнению команда.
  • Обратная связь (Feedback): При успешном переключении режима центральный контроллер обязательно должен отправить ответную телеграмму в статусный групповой адрес (например, `1/1/11`), чтобы зажечь LED-индикатор на аппаратной кнопке.
  • Физические кнопки (Modbus)

    Предположим, у входа в дом установлена панель со стандартными "сухими" контактами (кнопками без фиксации), подключенная к модулю дискретных входов, который опрашивается по шине RS-485 Modbus. Мы можем реализовать логику, при которой короткое нажатие выключает свет в прихожей, а длинное (удержание более 2 секунд) активирует общую команду или режим «Нет дома».

  • Опрос кнопки: Узел `Modbus-Read` с частотой 5-10 раз в секунду опрашивает регистр, отвечающий за состояние кнопки.
  • Подавление дребезга и детектор нажатия: Поток из узлов `rbe` (чтобы реагировать только на фактическое изменение состояния) и `trigger` используется для определения длительности физического нажатия.
  • Формирование команды: Узел `function` формирует стандартизированную команду на смену режима системы.
  • > 💡 Чек-лист тестирования аппаратных кнопок:

    > - [ ] Настроен ли антидребезг (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` Шаг 2: Настройка `switch` Шаг 3: Настройка `function`

    Этот узел получит команду от 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: Сбор данных с датчиков Шаг 2: Настройка узла `trigger`

    Это сердце нашей логики.

  • Установите режим: `send nothing`.
  • Затем `wait for`: `2 hours` (или другое необходимое время).
  • Затем `send`: `the latest msg`.
  • Важно: установите галочку `Extend delay if new message arrives`.
  • Это означает, что при каждом новом сообщении о движении таймер на 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), вы можете столкнуться с проблемой «спящего человека» — вы заснули на диване или читаете книгу без резких движений, а система решает, что в доме никого нет, и выключает свет.

    Чек-лист тестирования (Test Plan)

    Ожидать 2 часа для проверки автоматики неэффективно. При настройке обязательно проведите стресс-тест таймера на коротких промежутках времени:

    Реализация конечного автомата: Менеджер Режимов

    еализация конечного автомата: Менеджер Режимов

    Как мы подробно спроектировали в уроке COURSE-07-M02-L01 «Архитектура конечного автомата: управление режимами и приоритеты», для разрешения конфликтов между триггерами используется централизованный конечный автомат. Его задача — слушать все запросы от триггеров и принимать решение о смене режима на основе их приоритетов. Теперь реализуем его в Node-RED.

    Архитектура Менеджера Режимов

  • Децентрализованная генерация запросов: Все потоки-триггеры, которые мы создали (время, геопозиция, ручные кнопки, датчики), не меняют состояние системы напрямую. Они лишь публикуют стандартизированное сообщение с запросом и своим `priority` в единый MQTT-топик: `hi/system/mode/request`.
  • Централизованное принятие решений: Суб-поток «Менеджер Режимов» подписан на `hi/system/mode/request`. Это его единственная точка входа.
  • Логика приоритета: Получив запрос, менеджер сравнивает его приоритет с приоритетом источника, установившего текущий режим.
  • Установка состояния: Если приоритет нового запроса достаточен, менеджер обновляет глобальное состояние (`global context`) и публикует официальное, подтвержденное изменение в статусный MQTT-топик: `hi/system/mode/state`.
  • Реакция системы: Все остальные подсистемы (свет, климат, безопасность) подписаны только на `hi/system/mode/state` и реагируют на уже утвержденные изменения.
  • Скелет кода для узла `function` в «Менеджере Режимов»:
    // Имя узла: 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)

    Использование приоритетов элегантно решает проблему ручного вмешательства:

    Чек-лист и план тестирования

    Для проверки логики Менеджера Режимов проведите следующие тесты:

    Что дальше

    В этом уроке мы научились создавать триггеры для смены режимов и, что более важно, построили надежную архитектуру для управления ими на основе приоритетов. В следующем уроке мы перейдем к практической реализации сценариев, которые будут выполняться при смене этих глобальных режимов: что именно должен делать дом, когда он переходит в состояние «Ночь» или «Нет дома».