Паттерн 'Scheduler': запуск событий по расписанию
Введение в паттерн 'Scheduler'
> 💡 Подсказка: Паттерн 'Scheduler' (Планировщик) является основой для создания автономных систем, которые работают предсказуемо даже без вмешательства пользователя. Он переводит логику из «если произошло событие, то сделать X» в «в момент времени T сделать X».
В мире автоматизации зданий существует два фундаментальных типа триггеров, запускающих сценарии: событийные и временные. Событийные триггеры реагируют на внешние воздействия — нажатие настенного выключателя, срабатывание датчика движения, получение MQTT-сообщения. Временные же триггеры, наоборот, инициируют действия в строго определенные моменты времени. Именно для реализации таких триггеров и предназначен паттерн 'Scheduler'.
Scheduler (Планировщик) — это архитектурный подход к проектированию потоков (flows), при котором запуск логики автоматизации инициируется не внешним событием, а наступлением заданного времени или даты.Важность планирования в автоматизации трудно переоценить. Оно позволяет системе действовать проактивно, создавая комфорт и повышая энергоэффективность без постоянного участия человека.
📋 Ключевые понятия:
- Событийный триггер: Реакция на изменение. Пример: датчик открытия двери зажег свет в прихожей. Логика: `ЕСЛИ дверь открыта, ТО включить свет`.
- Временной триггер: Действие по расписанию. Пример: в 7:00 утра система плавно включает свет в спальне. Логика: `ЕСЛИ время = 07:00, ТО запустить сценарий "Утро"`.
Примеры задач, решаемых с помощью паттерна 'Scheduler':
- Утренние и вечерние сценарии: Плавное включение света, открытие штор, запуск кофеварки в 7:00 по будням.
- Управление климатом: Перевод системы отопления в экономичный режим в рабочее время (с 9:00 до 18:00) и возврат в комфортный режим за час до прихода жильцов.
- Автоматический полив: Запуск системы орошения газона на 30 минут три раза в неделю в 5:00 утра.
- Имитация присутствия: Хаотичное включение и выключение света и музыки в разных комнатах в вечернее время, когда дом находится в режиме «Отпуск».
- Сервисные операции: Ежедневная перезагрузка зависающего IP-оборудования в 3:00 ночи или еженедельное создание резервной копии конфигурации.
Для простых, статичных расписаний в Node-RED существует встроенный узел `Inject`. Однако его возможности ограничены. Он не позволяет динамически изменять расписание на лету, не поддерживает сложные правила (например, «каждую вторую пятницу месяца») и не умеет работать с астрономическими событиями, такими как восход и закат. Для профессиональных инсталляций его функционала часто оказывается недостаточно, что требует применения более мощных инструментов, которые мы рассмотрим далее.
---
Базовое планирование: узел 'Inject'
Узел `Inject` — это основной встроенный инструмент Node-RED для инициирования потоков. Хотя его чаще всего используют для ручного запуска и отладки, он обладает базовыми возможностями планировщика.
> ⚠️ Внимание: Узел 'Inject' использует системное время контроллера. Убедитесь, что часовой пояс на Debian установлен корректно командой `sudo timedatectl set-timezone Europe/Moscow`, иначе расписания будут срабатывать со сдвигом. Проверить текущие настройки можно командой `timedatectl`.
Рассмотрим два основных режима работы `Inject` как планировщика.
Режим 'interval': периодический запуск
Этот режим используется, когда действие нужно повторять через равные промежутки времени. Например, опрашивать Modbus-счетчик электроэнергии каждые 5 минут.
Настройка:Этот режим идеально подходит для периодического сбора телеметрии, но не для сценариев, привязанных к конкретному времени суток.
Режим 'at a specific time': запуск по времени
Этот режим позволяет настроить запуск в определенное время и, при необходимости, по определенным дням недели. Это основа для создания простых ежедневных или еженедельных сценариев.
Практический пример: включение уличного освещения Задача: Каждый день в 20:00 включать фасадную подсветку. Поток:`[Inject: 20:00 Daily]` -> `[Function: Create Command]` -> `[mqtt out: control/facade_light/set]`
Настройка узла `Inject`:Этот узел формирует сообщение в соответствии с принятым в системе контрактом сообщения, который мы рассматривали в предыдущих модулях.
// В этом узле мы не получаем данных от Inject,
// мы просто формируем команду на включение по факту его срабатывания.
msg.payload = {
"value": true, // true == ON
"source": "scheduler:facade_light_on",
"ts": Date.now()
};
// Устанавливаем топик для отправки в MQTT
msg.topic = "control/facade_light/set";
// Обновляем статус узла для визуальной диагностики
node.status({fill:"blue", shape:"dot", text:"Command ON sent at " + new Date().toLocaleTimeString()});
return msg;
Этот подход прост и надежен для фиксированных расписаний. Однако он имеет существенные недостатки:
- Статичность: Чтобы изменить время включения с 20:00 на 20:30, инженеру необходимо подключиться к контроллеру, открыть редактор Node-RED и вручную изменить настройки узла `Inject`, после чего выполнить `Deploy`. Это неприемлемо для конечного пользователя.
- Одно расписание на узел: Если вам нужно 10 разных расписаний, вам потребуется 10 узлов `Inject`, что загромождает поток и усложняет управление.
- Отсутствие астро-событий: Время заката меняется каждый день. Фиксированное время 20:00 будет оптимальным лишь короткий период в году. Узел `Inject` не умеет вычислять время восхода/заката.
Для решения этих проблем в профессиональных инсталляциях используется специализированный узел `cron-plus`.
---
Продвинутое планирование: узел 'cron-plus'
Когда возможности `Inject` исчерпаны, на сцену выходит `node-red-contrib-cron-plus`. Это мощный и гибкий узел-планировщик, ставший отраслевым стандартом для сложных задач автоматизации в Node-RED.
Установка:Синтаксис Cron
Ключевое преимущество `cron-plus` — использование стандартного синтаксиса Cron. Это формат командной строки для описания расписаний, широко используемый в Linux-системах. Выражение Cron состоит из 5 или 6 полей, разделенных пробелами.
| Поле | Допустимые значения | Символы |
| ------------------ | ----------------------- | ------------------------------------- |
| 1. Минуты | 0-59 | `*` (любое), `,` (перечисление), `-` (диапазон), `/` (шаг) |
| 2. Часы | 0-23 | `*` (любое), `,` (перечисление), `-` (диапазон), `/` (шаг) |
| 3. День месяца | 1-31 | `*` (любое), `,` (перечисление), `-` (диапазон) |
| 4. Месяц | 1-12 или JAN-DEC | `*` (любое), `,` (перечисление), `-` (диапазон) |
| 5. День недели | 0-7 или SUN-SAT (0 и 7=Вс) | `*` (любое), `,` (перечисление), `-` (диапазон) |
Примеры выражений Cron: ` ` — Каждую минуту. `0 8 ` — Каждый день в 8:00. `0 9,18 * 1-5` — В 9:00 и в 18:00 с понедельника по пятницу. `/15 ` — Каждые 15 минут. `0 8-18/2 * 1-5` — Каждый второй час с 8 утра до 18 вечера в рабочие дни.Ключевые преимущества 'cron-plus'
Эти возможности делают `cron-plus` незаменимым инструментом для любого инженера по автоматизации, работающего с платформой HI.
---
Практика с 'cron-plus': утренний сценарий
Рассмотрим создание классического сценария «Доброе утро» с помощью `cron-plus`.
Задача: По будням в 7:00 запускать утренний сценарий, который плавно включает свет в спальне и отправляет команду на включение кофеварки. Поток:`[cron-plus: Morning Routine]` -> `[switch: by msg.topic]` -> ...
Шаг 1: Настройка узла 'cron-plus'* Name: `Morning Scenario` (это имя для идентификации).
* Expression Type: `cron`.
Expression: `0 7 * 1-5` (что означает "в 0 минут 7-го часа, в любой день месяца, в любой месяц, с понедельника по пятницу").
* Payload: `JSON`. Введите `{ "command": "start_morning_scene" }`. Это и будет наше сообщение для дальнейшей обработки.
* Topic: `scheduler/morning`. Этот топик поможет нам маршрутизировать сообщение.
Сообщение, сгенерированное узлом, будет выглядеть примерно так:
{
"payload": {
"command": "start_morning_scene"
},
"topic": "scheduler/morning",
"_msgid": "...",
"cronplus": {
"triggerTimestamp": 1678852800000,
"name": "Morning Scenario",
"topic": "scheduler/morning",
"type": "cron",
"description": "at 07:00 on Monday, Tuesday, Wednesday, Thursday, Friday",
"payload": {
"command": "start_morning_scene"
},
"limits": [],
"command": "trigger",
"status": {
"state": "running",
"count": 1,
"next": "Tomorrow at 07:00",
"nextDescription": "завтра в 07:00"
}
}
}
Как видите, в `msg.cronplus` содержится вся необходимая диагностическая информация. `msg.payload` и `msg.topic` содержат то, что мы задали в настройках.
Шаг 3: Маршрутизация и исполнениеТеперь мы можем использовать узел `Switch` для направления этого события в нужную логическую ветку.
+--> [Flow: Bedroom Light Fade-in]
|
[cron-plus] -> [Switch: by topic] --+
|
+--> [Flow: Kitchen Coffee Maker ON]
* Если `msg.topic == "scheduler/morning"`, сообщение идет дальше.
* Если `msg.topic == "scheduler/evening"`, сообщение пойдет на другой выход (для другого расписания из этого же узла).
* Узел `Function` "Prepare Coffee Cmd":
// Принимает msg от cron-plus
// Формирует новую команду для розетки
msg.payload = {
"value": true,
"source": "scheduler:morning_coffee"
};
msg.topic = "control/kitchen/socket_coffee/set";
return msg;
* Узел `mqtt out`: Отправляет эту команду брокеру.
Таким образом, один узел `cron-plus` становится центральным диспетчером для всех временных сценариев на объекте.
---
Динамические расписания и Solar Events
Самые мощные функции `cron-plus` — это способность управляться извне и работать с астрономическими событиями.
> 💡 Подсказка: Для получения точных координат вашего объекта используйте онлайн-карты. Эти значения вводятся один раз в узле конфигурации `cron-plus` и используются для всех астро-событий.
Управление расписаниями через входящие сообщения
Вы можете добавлять, изменять, удалять, приостанавливать и запрашивать список расписаний, отправляя на вход `cron-plus` специально сформированное сообщение. Команда передается в `msg.payload`.
Структура команды `add`:{
"command": "add",
"name": "temporary-sprinkler-wednesday",
"expression": "0 5 3",
"expressionType": "cron",
"payload": {"command": "start_sprinkler", "zone": "lawn_front", "duration_min": 20},
"topic": "scheduler/gardening"
}
Отправив такое сообщение (например, из узла `http in` после запроса от веб-интерфейса), вы создадите новое расписание "на лету".
Пример: Временный поливПредставим, что у пользователя есть дашборд, где он может задать полив на ближайшие дни.
Другие команды:
- `"command": "remove", "name": "temporary-sprinkler-wednesday"` — удаление расписания.
- `"command": "list"` — на выходе узел выдаст массив всех текущих расписаний.
Использование Solar Events
Это "золотой стандарт" для управления уличным и фасадным освещением.
Настройка:* `Name:` Facade Light ON
* `Expression Type:` solar
* `Expression:` `sunset` `15` (15 минут после заката)
* `Payload:` `{"value": true}`
* `Topic:` `control/facade_light/set`
* `Name:` Facade Light OFF
* `Expression Type:` cron
`Expression:` `30 23 `
* `Payload:` `{"value": false}`
* `Topic:` `control/facade_light/set`
Поток: +--[Schedule "Facade ON"]--+
[cron-plus] --+ +--> [mqtt out: control/facade_light/set]
+--[Schedule "Facade OFF"]--+
Этот простой и элегантный поток решает задачу адаптивного освещения, которое не требует перенастройки в течение года.
---
Интеграция с другими паттернами
Паттерн 'Scheduler' достигает максимальной эффективности, когда используется в комбинации с другими паттернами проектирования, такими как 'State Machine' (Конечный автомат) и 'Gate' (Вентиль).
> 🔗 Связанный материал: Для глубокого понимания управления состояниями, обратитесь к уроку `COURSE-06-M05-L03` 'Паттерн State Machine', а для блокировки потоков — к `COURSE-06-M05-L01` 'Паттерн Gate'.
Комбинация 'Scheduler' и 'State Machine'
Вместо того чтобы планировщик напрямую управлял устройствами, он должен управлять состоянием системы. Это делает автоматизацию более гибкой и контекстно-зависимой.
Пример: Управление отоплением.- Неправильный подход:
* Расписание в 18:00 отправляет команду: `set temperature to 22°C`.
* Проблема: Если в 10:00 утра пользователь вручную выставит 23°C, его настройка будет работать до 18:00, когда сработает второе расписание и снова изменит температуру, игнорируя желание пользователя.
- Правильный подход (с State Machine):
2. `cron-plus` в 9:00 не отправляет команду термостату. Вместо этого он отправляет сообщение, которое меняет состояние: `flow.system_mode = "away"`.
3. `cron-plus` в 18:00 меняет состояние на `flow.system_mode = "home"`.
4. Отдельный, независимый поток следит за изменением `flow.system_mode`. Когда состояние меняется на "away", он устанавливает уставку 18°C. Когда на "home" — 22°C.
* Преимущество: Если пользователь вручную меняет температуру, он может также изменить и состояние (например, на `manual_override`). Теперь планировщик, меняя `system.mode`, не затронет ручную настройку, пока система не будет возвращена в автоматический режим.
Использование паттерна 'Gate' для временной блокировки расписаний
Паттерн 'Gate' позволяет временно блокировать прохождение сообщений. Это идеальный способ деактивировать расписания без их удаления.
Практический кейс: режим "Отпуск" Задача: Когда система переведена в режим «Отпуск», все бытовые сценарии (утренний, вечерний) не должны срабатывать, но сценарий имитации присутствия должен работать. Решение:* Этот `gate` настроен так, что он «закрыт», если `flow.system_mode == "vacation"`.
* Когда `cron-plus` отправляет сообщение, `gate` его просто блокирует и не пропускает дальше.
Поток:`[cron-plus: Morning]` -> `[Gate: Check Vacation Mode]` -(blocked if vacation)-> `[Flow: Morning Scene]`
Этот подход гораздо элегантнее, чем динамическое удаление и добавление десятков расписаний при переключении режимов. Он позволяет централизованно управлять активностью целых групп сценариев, просто меняя одну переменную состояния. Расписания остаются в конфигурации, но их действие временно приостановлено.
Что дальше
В этом уроке мы детально изучили паттерн 'Scheduler', от базового `Inject` до мощного `cron-plus`, научились создавать статические, динамические и астрономические расписания, а также интегрировать их с другими паттернами для создания надежной и гибкой автоматизации. В следующем уроке мы перейдем к рассмотрению паттерна 'Splitter/Aggregator', который позволяет эффективно работать с массивами данных и множественными ответами от устройств.