Шаг 3: Реализация 3+ аварийных и 5+ штатных сценариев
Введение: Capstone Project — финальная сборка системы
ведение: Capstone Project — финальная сборка системы
Поздравляем! Вы добрались до финального проекта этого курса — урока, где все изученные нами концепции и разработанные механизмы объединятся в единую, интеллектуальную и оркестрованную систему умного дома. До этого момента мы планомерно создавали ее составные части.
Ретроспектива: Наш путь по курсу
Вспомним пройденные этапы разработки нашей системы (модули M01-M09):
- M01: Мы спроектировали «мозг» системы — граф состояний, определив ключевые режимы и их приоритеты.
- M02-M03: Мы воплотили этот граф в коде, создав центральный конечный автомат (FSM) и механизм его журналирования, способный принимать решения и логировать их.
- M04-M05: Мы разработали критически важные, но пока автономные сценарии: надежный аварийный сценарий «Протечка» и сложный детектор присутствия для режима «Никого нет».
- M08-M09: Мы настроили взаимодействие с исполнительными устройствами через разные протоколы, такие как Modbus, DALI и MQTT.
Архитектура финальной сборки
Теперь наша задача — провести финальную сборку. Мы перестанем создавать новое как автономные элементы и займемся сквозной интеграцией. Мы "сошьем" все ранее разработанные потоки с нашим центральным FSM, следуя четкой архитектуре:
Эта "сборка" и есть суть нашего сценарного слоя. Мы отказываемся от хаотичных прямых связей в пользу строгого порядка, где FSM выступает главным дирижером.
> 🔗 Связанный материал: Убедитесь, что ваш конечный автомат (FSM) из модуля `M02` и система журналирования из `M03` полностью развернуты и работают. Вся логика, которую мы будем сегодня интегрировать, будет взаимодействовать именно с ними.
Практика: Шаг 1. Интеграция аварийного сценария 'Протечка воды'
рактика: Шаг 1. Интеграция аварийного сценария 'Протечка воды'
Сейчас у нас есть поток, который при срабатывании датчика напрямую отправляет команду на закрытие кранов. Это надежно, но не вписывается в новую архитектуру. Мы разделим его на две части: запрос и действие.
Задача: Адаптировать существующий сценарий «Протечка» (из модуля `M04`). Поток обнаружения должен запрашивать у FSM переход в состояние `LEAK`. Исполнительная логика (закрытие кранов, отправка уведомлений) должна быть вынесена в отдельный поток, реагирующий на успешный переход в это состояние.> ⚠️ Внимание: Разделение на "запрос" и "действие" не снижает надежности. Оба потока работают локально на контроллере. Мы лишь добавляем логический слой арбитража, что критически важно для предотвращения конфликтов в более сложных системах.
Часть A: Модификация потока обнаружения
Откройте ваш flow со сценарием протечки. Сейчас он выглядит примерно так: `[mqtt in: leak sensor]` -> `[function: prepare command]` -> `[modbus-write: close valve]`. Мы должны его изменить.
// Получаем payload от датчика
const data = msg.payload;
// Реагируем только на событие "протечка обнаружена"
if (data.value !== true) {
return null; // Игнорируем сообщения "протечка устранена"
}
// Формируем стандартизированное сообщение для state machine
msg.payload = {
command: "setState",
payload: {
state: "LEAK", // Запрашиваемое состояние
priority: 100, // Максимальный приоритет из нашего графа
source: data.source // Источник события для журнала
}
};
// Устанавливаем топик, который слушает наша state machine
msg.topic = "system/fsm/command";
node.status({fill:"red", shape:"dot", text:"LEAK detected! Requesting FSM..."});
return msg;
> 💡 Почему приоритет 100? Аварийные состояния (протечка, пожар, вторжение) должны безоговорочно вытеснять любые другие режимы (override). Если в момент протечки активирован сценарий набора ванной (приоритет 50) или базовый режим `HOME` (приоритет 10), высокоприоритетный `LEAK` перебьет их и заблокирует смену состояний на более низкие, пока авария не будет устранена.
Часть B: Создание центрального потока действий
Теперь создадим (или дополним) поток, который будет выполнять действия на основе команд от FSM.
* Правило 1: `==` "LEAK" -> Выход 1
* Правило 2: `==` "AWAY" -> Выход 2
* Правило 3: `==` "EVENING" -> Выход 3
* ...и так далее для всех состояний.
* Узел `function` (имя: "Prepare Valve Close Command"):
// Закрываем вводной кран холодной воды (например, Coil 10)
msg.payload = {
'value': true, 'fc': 5, 'unitid': 1, 'address': 10
};
// Возвращаем второе сообщение для горячей воды (Coil 11)
const msgHot = {
payload: {
'value': true, 'fc': 5, 'unitid': 1, 'address': 11
}
};
node.status({ fill:"red", shape:"ring", text:"Executing LEAK actions!" });
return [msg, msgHot];
* Узел `modbus-write`: Подключите два узла `modbus-write` к выходам `function`, каждый для своего крана.
* Узел Уведомления: Параллельно подключите узел для отправки тревожного SMS/Push сообщения.
Теперь сценарий протечки полностью интегрирован в новую архитектуру.
Часть C: UX и сброс состояния (Override)
Аварийный сценарий `LEAK` обладает наивысшим приоритетом, поэтому система не сможет самостоятельно вернуться в обычный режим (например, `HOME`), даже если датчик перестал фиксировать воду. Это осознанное UX/override решение: пользователь должен явно подтвердить, что проблема устранена, осмотрев место протечки.
Для реализации этого сброса (clear override) добавьте отдельный небольшой поток:
msg.payload = {
command: "clearState",
payload: {
state: "LEAK"
}
};
msg.topic = "system/fsm/command";
return msg;
Часть D: Чек-лист проверки интеграции
Чтобы убедиться, что новая логика отрабатывает корректно и нет логических разрывов, выполните следующий тест-план через инъекции (Inject nodes) в Node-RED:
- [ ] Тест детекции: Сымитировать payload от датчика протечки со значением `{value: true}`.
- [ ] Проверка FSM: Убедиться (через Debug-узел на выходе FSM), что текущее состояние изменилось на `LEAK`.
- [ ] Проверка действий: Подтвердить, что узлы `modbus-write` получили верные объекты команд (проверяйте адреса регистров и unitid).
- [ ] Тест аппаратной обратной связи: Убедиться, что интерфейс Dashboard (если используется) синхронизировался и отображает закрытое состояние кранов.
- [ ] Тест вытеснения (Override): При активном состоянии `LEAK` отправить команду на переход в состояние `EVENING` (приоритет 20). Убедиться, что FSM отклонил смену состояния, сохранив `LEAK`.
- [ ] Сброс состояния: Отправить команду `clearState` (через созданную кнопку сброса) и убедиться, что система вышла из аварийного режима, вернулась к штатным приоритетам, а краны готовы к ручному открытию.
Практика: Шаг 2. Интеграция штатного режима 'Никого нет' (Away)
рактика: Шаг 2. Интеграция штатного режима 'Никого нет' (Away)
Повторим ту же процедуру для сценария "Никого нет", который мы создали ранее в курсе (модуль `M05`). Его базовая логика была основана на узле `trigger`, и по истечении таймера он напрямую выключал свет и климат.
Задача: Изменить поток детектора отсутствия так, чтобы он запрашивал состояние `AWAY` у конечного автомата, а не управлял устройствами напрямую. Затем добавить в центральный поток действий новую ветку для выполнения всех команд этого режима.> 💡 Подсказка: Комбинирование нескольких источников для определения присутствия (Wi-Fi, BLE, движение) остается актуальным. Эти источники по-прежнему являются входами для узла `trigger`. Мы меняем только то, что происходит после `trigger`.
Часть A: Модификация потока детектора отсутствия
// Этот код выполняется, когда trigger-нода сообщила об отсутствии движения 15 мин.
// Предполагаем, что состояние входной двери и текущее состояние FSM
// доступны в контексте (flow.get), обновляемые другими потоками.
const doorState = flow.get("door_front_state") || { value: "unknown" };
const systemState = flow.get("systemState") || { priority: 0 };
// Задел для UX-исключения (проверка активности медиа-системы)
const tvPower = flow.get("tv_power_consumption_w") || 0;
const isTvOn = tvPower > 30; // Если потребляем больше 30Вт, кто-то смотрит ТВ
// ПРЕДУСЛОВИЯ:
// 1. Дверь закрыта
// 2. Нет более приоритетного состояния (например, FIRE или LEAK)
// 3. Телевизор не работает (override-защита)
if (doorState.value === false && systemState.priority < 10 && !isTvOn) {
// Условия выполнены. Формируем запрос для FSM.
msg.payload = {
command: "setState",
payload: {
state: "AWAY",
priority: 10, // Штатный приоритет режима 'Никого нет'
source: "Absence Detector Scenario"
}
};
msg.topic = "system/fsm/command";
node.status({fill:"blue", shape:"dot", text:"Requesting AWAY mode"});
return msg;
} else {
// Условия не выполнены, отменяем переход
node.status({fill:"yellow", shape:"ring", text:"AWAY blocked. Door open, TV on, or priority conflict."});
return null;
}
Часть B: Добавление действий для 'AWAY' в центральный поток
Вернитесь к нашему центральному потоку действий (который начинается с `mqtt in: system/state/changed` и обрабатывает подтвержденные состояния).
* Узел `function` (имя: "Prepare AWAY commands"): формирует сразу несколько команд для разных систем умного дома.
// Подготовка пакета команд для режима AWAY
const lightOff = { payload: "OFF", topic: "zigbee2mqtt/light_group_all/set/state" };
const socketsOff = { payload: "OFF", topic: "zigbee2mqtt/socket_group_media/set/state" };
const climateEco = {
payload: JSON.stringify({
system_mode: "heat",
temperature_setpoint_hold: true,
occupied_heating_setpoint: 18
}),
topic: "zigbee2mqtt/thermostat_livingroom/set"
};
node.status({ fill:"blue", shape:"ring", text:"Executing AWAY actions!" });
// Возвращаем вложенный массив. Каждый элемент массива уйдет на отдельный выход.
// Если выход один - передаем массив объектов, и узел mqtt out отработает их последовательно.
return [[lightOff, socketsOff, climateEco]];
* Узел `mqtt out`: Подключите к выходу `function` узел `mqtt out` (оставьте topic пустым, так как он задается внутри `msg.topic` для каждого устройства).
Часть C: Чек-лист проверки и тест-план
Перед тем как двигаться дальше, проведите тестирование новой архитектуры по расширенному тест-плану:
- [ ] Штатное срабатывание: Дверь закрыта, а датчики движения (и/или Wi-Fi) не активны 15 мин → система генерирует команду перехода в `AWAY`.
- [ ] Цепочка действий: Центральный маршрутизатор ловит новое состояние от FSM и рассылает команды `OFF` свету и целевым розеткам.
- [ ] Энергосбережение: Термостат успешно переключается в экономичный режим сохранения тепла (18°C).
- [ ] Тест прерывания таймера: Если во время этих 15 минут ожидания фиксируется движение — таймер узла `trigger` корректно сбрасывается или обнуляется, команда `AWAY` не отправляется.
- [ ] Блокировка по двери: Откройте дверь и переждите 15 минут без движения. Запрос на `AWAY` должен быть заблокирован условием `doorState.value === false`.
UX-исключения и Override-механизмы
> ⚠️ UX/Override: Главная проблема таймеров отсутствия — ложные срабатывания, когда вы дома, но находитесь в статичном положении (например, лежите неподвижно перед телевизором, играете в консоль или читаете книгу). Датчики движения могут вас "потерять".
Для предотвращения ложного запуска `AWAY` мы интегрировали в код "Check & Request AWAY" проверку исключений:
- Если телевизор или ПК включен (определяется по уровню энергопотребления умной розетки — `tvPower > 30`), переход в режим "Никого нет" жестко блокируется, пока медиа-система активна.
- В будущем сюда можно добавить проверку статуса сна (`SLEEP`), чтобы система не выключала климат, пока вы спите.
Практика: Шаг 3. Интеграция контекстно-зависимого сценария 'Вечер'
рактика: Шаг 3. Интеграция контекстно-зависимого сценария 'Вечер'
Этот сценарий на основе `cron-plus` (из модуля `M06`) запускается по заходу солнца. Ранее он мог напрямую включать какую-то световую сцену. Теперь он будет запрашивать состояние `EVENING`.
> ℹ️ Информация: Перевод системы в состояние `EVENING` гораздо мощнее, чем простое включение света. На это состояние могут быть "подписаны" и другие системы: например, шторы могут закрыться, а музыкальный плейлист смениться на вечерний. FSM позволяет централизованно анонсировать это событие.
Часть A: Модификация потока-триггера
// Этот код выполняется по заходу солнца
const systemState = flow.get("systemState") || { state: "UNKNOWN" };
// Вечерний режим имеет смысл только если кто-то дома (состояние HOME или уже EVENING)
const allowedStates = ["HOME", "GUESTS"];
if (allowedStates.includes(systemState.state)) {
// Запрашиваем переход в состояние EVENING
msg.payload = {
command: "setState",
payload: {
state: "EVENING",
priority: 25,
source: "Sunset Scenario"
}
};
msg.topic = "system/fsm/command";
return msg;
} else {
// Никого нет дома или активен более приоритетный режим — ничего не делаем
node.status({ fill:"grey", shape:"dot", text:"Blocked, state is " + systemState.state});
return null;
}
Часть B: Добавление действий для 'EVENING'
Снова возвращаемся в центральный поток действий.
msg.payload = {
command: "scene",
address: "g1", // Группа светильников гостиной
value: 5, // Вызвать сцену №5 («Вечер»)
fadeTime: 10 // Плавное включение за 10 секунд
};
node.status({ fill:"magenta", shape:"ring", text:"Executing EVENING actions!" });
return msg;
Часть C: UX и обработка ручного вмешательства (Override)
> ⚠️ Важно: Переход в состояние `EVENING` не должен вызывать дискомфорт у пользователей, если они уже переопределили освещение вручную (например, включив яркий свет для чтения).
Для обеспечения качественного UX при реализации контекстных сценариев рекомендуется учитывать текущий статус периферии. Возможны два подхода к управлению:
- Жесткий (Безусловный): Сценарий всегда применяет сцену №5, сбрасывая ручные настройки. Это допустимо для транзитных зон (коридоры, холлы, лестницы).
- Мягкий (UX-friendly): Перед отправкой команды `scene` узел-обработчик сверяется с регистром "ручного переопределения" (`override flag`) для конкретной комнаты. Если флаг активен (пользователь недавно настраивал свет сам), автоматическая смена сцены для этой комнаты блокируется или откладывается.
Чек-лист проверки сценария 'Вечер'
- [ ] Триггер `cron-plus` корректно генерирует событие `sunset` (при отладке временно используйте узел `inject` для эмуляции заката).
- [ ] Узел "Check & Request EVENING" успешно блокирует отправку команды смены режима, если актуальный стейт системы равен `AWAY` (Никого нет дома).
- [ ] При статусах `HOME` или `GUESTS` генерируется корректный JSON-запрос в топик `system/fsm/command` с корректным приоритетом (25).
- [ ] Маршрутизатор (узел `switch`) правильно направляет сообщения на обработчик "Prepare DALI Evening Scene".
- [ ] Осветительные приборы реагируют плавно (`fadeTime: 10`), без резких световых скачков.
Практика: Шаг 4. Сквозное тестирование интегрированной системы
рактика: Шаг 4. Сквозное тестирование интегрированной системы
Теперь, когда все потоки "сшиты", необходимо провести сквозное тестирование, чтобы убедиться, что вся цепочка работает корректно без необходимости физически взаимодействовать с датчиками (бегать с мокрой тряпкой к датчику протечки или ждать реального заката).
Шаг 1: Использование «виртуального пульта»
Узлы `inject` — ваш главный инструмент для unit- и интеграционного тестирования в Node-RED. Создайте на отдельной вкладке "Testing" паттерн из нескольких узлов `inject`, которые будут имитировать реальные события. Это позволит проверить реакцию системы на любые вводные данные:
- Симуляция протечки (Авария, Высший приоритет):
* `Payload` (JSON): `{"value": true, "source": "VIRTUAL_leak_detector_bathroom"}`
- Симуляция отсутствия движения (Штатный сценарий):
* `Payload` (JSON): `{"event": "no_motion_for_15_min"}`
Примечание:* для быстрого теста узла `trigger` временно уменьшите задержку с 15 минут до 10 секунд.
- Симуляция заката/рассвета (События расписания):
- Сброс аварии (Возврат в штатный режим):
* `Payload` (JSON): `{"action": "reset", "initiator": "VIRTUAL_admin"}`
Шаг 2: Анализ журнала событий FSM и проверка Override
Журнал событий конечного автомата (созданный нами ранее в рамках сквозного пути по курсу), теперь становится бесценным инструментом для отладки логики арбитража. Проведите последовательный тест (Test Plan), чтобы проверить механизм Override:
INFO: State transition requested. Source: VIRTUAL_leak_detector_bathroom, Target: LEAK, Priority: 100.
INFO: Current state is HOME (Priority 20). New priority is higher. Transition ALLOWED.
SUCCESS: System state changed from HOME to LEAK.
INFO: State transition requested. Source: Absence Detector Scenario, Target: AWAY, Priority: 10.
WARN: Current state is LEAK (Priority 100). New priority is lower. Transition DENIED.
Этот тест доказывает работоспособность архитектуры: конечный автомат (FSM) корректно выполняет арбитраж и предотвращает применение штатных или низкоприоритетных сценариев во время аварии (логика Override отрабатывает идеально, надежно защищая дом).
Шаг 3: Маршрутизация и использование `debug` узлов
Для визуального контроля прохождения сигналов в реальном времени расставьте узлы `debug` в критических точках маршрута:
- [ ] Отслеживание триггеров: Установите `debug` сразу после `mqtt in` (топик `system/state/changed`) в центральном потоке действий.
- [ ] Контроль маршрутизатора: На каждом выходе из `switch` ("Route by State"), чтобы четко фиксировать, какая ветка сценариев (HOME, AWAY, LEAK) активировалась.
- [ ] Проверка исходящих команд: На выходах `function` узлов, формирующих команды для аппаратных контроллеров (DALI для света, Modbus для реле кранов, MQTT для других устройств), до их фактической отправки в сеть.
> 💡 Подсказка: В свойствах узла `debug` измените настройку Output с `msg.payload` на `complete msg object`. Это позволит вам видеть не только тело сообщения, но и `msg.topic`, что критически важно при отладке MQTT-маршрутизации.
> ⚠️ Важно: После успешного завершения сквозного тестирования обязательно деактивируйте (кнопкой на самом узле) или удалите вспомогательные `debug` узлы. Оставленные активными, они будут забивать системный журнал Node-RED и впустую расходовать ресурсы процессора (CPU и RAM) контроллера в production-среде.
Заключение: Система в сборе
аключение: Система в сборе
Поздравляем! Вы завершили самый важный этап — сборку. Мы взяли разрозненные, но функциональные потоки и оркестровали их с помощью центрального конечного автомата. Мы на практике реализовали паттерн "Запрос → Арбитраж → Действие".
Ваш путь по курсу
Оглядываясь назад, важно оценить пройденный путь по курсу. Архитектура умного дома не появилась из ниоткуда, она стала результатом последовательных шагов по проектированию и интеграции. Наша ретроспектива охватывает ключевые этапы (от M01 до M09):
- M01–M03 (Проектирование и основы): Мы заложили фундамент, определили базовые потребности, нарисовали первичные схемы графов состояний и научились правильно обрабатывать потоки данных.
- M04–M06 (Сенсорика и исполнители): Мы подключили и откалибровали датчики (детекторы событий), подготовили исполнительные механизмы и настроили надежную маршрутизацию сообщений между ними.
- M07–M09 (Оркестрация и интерфейсы): Мы перешли от разрозненных реакций к полноценному конечному автомату (FSM), внедрили систему арбитража конфликтов на базе приоритетов и объединили всё в единый центральный поток, добавив возможность пользовательского контроля.
Ключевой результат этой работы:
- Изоляция логики: Детекторы событий не знают, как выполнять действия. Исполнители не знают, почему их вызвали. Каждый занимается своим делом.
- Предсказуемость: Конфликты между сценариями (например, когда датчик движения срабатывает во время режима сна) теперь разрешаются автоматически на основе приоритетов. Система всегда будет вести себя ожидаемо.
- Масштабируемость: Добавление нового сценария теперь сводится к трем простым шагам: создать поток-инициатор (запрос), добавить состояние в FSM (если нужно) и добавить ветку в центральный поток действий. Старые сценарии при этом переписывать не нужно.
Созданная архитектура является надежным фундаментом для дальнейшего наращивания функциональности умного дома. Любые новые потребности будут органично вписываться в существующую структуру, не создавая спагетти-кода и хаоса в маршрутизации.
> 💡 Связанный материал: Обновите ваш граф состояний из модуля M01. Теперь он не просто теоретическая схема, а точное отражение работающей системы. Эта живая документация будет незаменима при дальнейшей поддержке и развитии проекта.
Что дальше?
На следующем, финальном, шаге нашего проекта мы создадим простой, но эффективный пользовательский интерфейс с помощью `node-red-dashboard`. Мы разработаем дашборд, который позволит пользователю:
- Видеть текущее состояние системы в реальном времени.
- Просматривать журнал важных событий (аварий и смен режимов).
- Главное: вручную переключать режимы, осуществляя тот самый ручной перехват (override), который мы закладывали в архитектуру с самого начала. Вы увидите, как ручное вмешательство в интерфейсе (UX) корректно перекрывает автоматику благодаря наивысшему приоритету, не ломая при этом логику FSM.