ГлавнаяАкадемияNode-RED: установка, flows, msg/JSON, отладка → Паттерн 'Scheduler': запуск событий по расписанию

Паттерн 'Scheduler': запуск событий по расписанию

Урок 3 · Node-RED: установка, flows, msg/JSON, отладка · 30 мин · theory

Введение в паттерн 'Scheduler'

> 💡 Подсказка: Паттерн 'Scheduler' (Планировщик) является основой для создания автономных систем, которые работают предсказуемо даже без вмешательства пользователя. Он переводит логику из «если произошло событие, то сделать X» в «в момент времени T сделать X».

В мире автоматизации зданий существует два фундаментальных типа триггеров, запускающих сценарии: событийные и временные. Событийные триггеры реагируют на внешние воздействия — нажатие настенного выключателя, срабатывание датчика движения, получение MQTT-сообщения. Временные же триггеры, наоборот, инициируют действия в строго определенные моменты времени. Именно для реализации таких триггеров и предназначен паттерн 'Scheduler'.

Scheduler (Планировщик) — это архитектурный подход к проектированию потоков (flows), при котором запуск логики автоматизации инициируется не внешним событием, а наступлением заданного времени или даты.

Важность планирования в автоматизации трудно переоценить. Оно позволяет системе действовать проактивно, создавая комфорт и повышая энергоэффективность без постоянного участия человека.

📋 Ключевые понятия:

Примеры задач, решаемых с помощью паттерна 'Scheduler':

Для простых, статичных расписаний в Node-RED существует встроенный узел `Inject`. Однако его возможности ограничены. Он не позволяет динамически изменять расписание на лету, не поддерживает сложные правила (например, «каждую вторую пятницу месяца») и не умеет работать с астрономическими событиями, такими как восход и закат. Для профессиональных инсталляций его функционала часто оказывается недостаточно, что требует применения более мощных инструментов, которые мы рассмотрим далее.

---

Базовое планирование: узел 'Inject'

Узел `Inject` — это основной встроенный инструмент Node-RED для инициирования потоков. Хотя его чаще всего используют для ручного запуска и отладки, он обладает базовыми возможностями планировщика.

> ⚠️ Внимание: Узел 'Inject' использует системное время контроллера. Убедитесь, что часовой пояс на Debian установлен корректно командой `sudo timedatectl set-timezone Europe/Moscow`, иначе расписания будут срабатывать со сдвигом. Проверить текущие настройки можно командой `timedatectl`.

Рассмотрим два основных режима работы `Inject` как планировщика.

Режим 'interval': периодический запуск

Этот режим используется, когда действие нужно повторять через равные промежутки времени. Например, опрашивать Modbus-счетчик электроэнергии каждые 5 минут.

Настройка:
  • Откройте узел `Inject`.
  • В поле `Payload` установите значение, которое будет отправляться. Например, пустая строка.
  • В поле `Topic` можно указать тему, идентифицирующую этот триггер, например, `scheduler/poll/energymeter`.
  • Ниже, в секции `Repeat`, выберите из выпадающего списка `interval`.
  • Установите необходимый интервал, например, `every 5 minutes`.
  • Опционально можно включить галочку `Inject once at start?`, чтобы поток выполнился один раз сразу после развертывания или перезагрузки контроллера.
  • Этот режим идеально подходит для периодического сбора телеметрии, но не для сценариев, привязанных к конкретному времени суток.

    Режим 'at a specific time': запуск по времени

    Этот режим позволяет настроить запуск в определенное время и, при необходимости, по определенным дням недели. Это основа для создания простых ежедневных или еженедельных сценариев.

    Практический пример: включение уличного освещения Задача: Каждый день в 20:00 включать фасадную подсветку. Поток:

    `[Inject: 20:00 Daily]` -> `[Function: Create Command]` -> `[mqtt out: control/facade_light/set]`

    Настройка узла `Inject`:
  • В секции `Repeat` выберите `at a specific time`.
  • В поле `At` введите время `20:00`.
  • В секции `On days` оставьте отмеченными все дни недели.
  • Настройка узла `Function`:

    Этот узел формирует сообщение в соответствии с принятым в системе контрактом сообщения, который мы рассматривали в предыдущих модулях.

    // В этом узле мы не получаем данных от 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;

    Этот подход прост и надежен для фиксированных расписаний. Однако он имеет существенные недостатки:

    Для решения этих проблем в профессиональных инсталляциях используется специализированный узел `cron-plus`.

    ---

    Продвинутое планирование: узел 'cron-plus'

    Когда возможности `Inject` исчерпаны, на сцену выходит `node-red-contrib-cron-plus`. Это мощный и гибкий узел-планировщик, ставший отраслевым стандартом для сложных задач автоматизации в Node-RED.

    Установка:
  • Откройте меню Node-RED (☰ в правом верхнем углу).
  • Выберите `Manage palette`.
  • Перейдите на вкладку `Install`.
  • В строке поиска введите `cron-plus` и нажмите `Install` напротив `node-red-contrib-cron-plus`.
  • Синтаксис 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` может содержать десятки независимых расписаний. Каждое со своим именем, Cron-выражением, `payload` и `topic`. Это делает поток компактным и управляемым.
  • Динамическое управление: Расписаниями можно управлять "на лету" с помощью входящих `msg`. Можно добавлять, удалять, приостанавливать и возобновлять расписания без необходимости `Deploy`. Это открывает возможность для пользователя настраивать автоматизацию через интерфейс (например, Dashboard или мобильное приложение).
  • Астрономические события (Solar Events): `cron-plus` может вычислять время восхода (`sunrise`), заката (`sunset`), гражданских сумерек (`dusk`, `dawn`) и других астрономических явлений для заданных географических координат. Это позволяет создавать адаптивное освещение, которое работает идеально круглый год.
  • Подробная информация о запуске: При срабатывании узел добавляет в сообщение объект `msg.cronplus`, содержащий массу полезной информации: имя сработавшего расписания, описание, время следующего запуска и т.д., что упрощает отладку и создание сложной логики.
  • Эти возможности делают `cron-plus` незаменимым инструментом для любого инженера по автоматизации, работающего с платформой HI.

    ---

    Практика с 'cron-plus': утренний сценарий

    Рассмотрим создание классического сценария «Доброе утро» с помощью `cron-plus`.

    Задача: По будням в 7:00 запускать утренний сценарий, который плавно включает свет в спальне и отправляет команду на включение кофеварки. Поток:

    `[cron-plus: Morning Routine]` -> `[switch: by msg.topic]` -> ...

    Шаг 1: Настройка узла 'cron-plus'
  • Перетащите узел `cron-plus` на поле.
  • Откройте его настройки. Дайте узлу имя, например, `Global Schedulers`.
  • Нажмите кнопку `Add`, чтобы добавить новое расписание.
  • * Name: `Morning Scenario` (это имя для идентификации).

    * Expression Type: `cron`.

    Expression: `0 7 * 1-5` (что означает "в 0 минут 7-го часа, в любой день месяца, в любой месяц, с понедельника по пятницу").

    * Payload: `JSON`. Введите `{ "command": "start_morning_scene" }`. Это и будет наше сообщение для дальнейшей обработки.

    * Topic: `scheduler/morning`. Этот топик поможет нам маршрутизировать сообщение.

  • Нажмите `Done`, затем `Deploy`. Теперь ровно в 7:00 в будний день узел сгенерирует сообщение.
  • Шаг 2: Обработка сообщения

    Сообщение, сгенерированное узлом, будет выглядеть примерно так:

    {
    

    "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]

  • Узел `Switch` настраивается на проверку `msg.topic`.
  • * Если `msg.topic == "scheduler/morning"`, сообщение идет дальше.

    * Если `msg.topic == "scheduler/evening"`, сообщение пойдет на другой выход (для другого расписания из этого же узла).

  • Далее поток разветвляется. Одно сообщение запускает субпоток (Subflow), который, например, в течение 5 минут плавно повышает яркость света в спальне с 0% до 80%.
  • Другая ветка отправляет MQTT-команду на умную розетку, к которой подключена кофеварка.
  • * Узел `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` после запроса от веб-интерфейса), вы создадите новое расписание "на лету".

    Пример: Временный полив

    Представим, что у пользователя есть дашборд, где он может задать полив на ближайшие дни.

  • Пользователь в интерфейсе выбирает "Поливать газон в среду и пятницу в 5 утра".
  • Интерфейс формирует и отправляет на контроллер HI два JSON-сообщения, аналогичных приведенному выше (одно для среды, другое для пятницы).
  • `http in` узел в Node-RED принимает эти запросы и передает их напрямую в `cron-plus`.
  • `cron-plus` добавляет два новых расписания. После выполнения они могут быть автоматически удалены, если указать `limit: 1` в команде `add`.
  • Другие команды:

    Использование Solar Events

    Это "золотой стандарт" для управления уличным и фасадным освещением.

    Настройка:
  • В настройках узла `cron-plus` найдите секцию `Solar Events` и введите широту (`Latitude`) и долготу (`Longitude`) вашего объекта.
  • Теперь в настройках расписания вы можете выбрать `Expression Type: solar`.
  • В поле `Expression` можно выбрать события: `sunrise` (восход), `sunset` (закат), `dawn` (рассвет), `dusk` (сумерки) и т.д.
  • Можно также задавать смещение, например, `sunset` `-30` (за 30 минут до заката).
  • Практический кейс: Задача: Включать фасадную подсветку через 15 минут после заката и выключать в 23:30. Решение: В одном узле `cron-plus` создаются два расписания.
  • Расписание "Facade ON":
  • * `Name:` Facade Light ON

    * `Expression Type:` solar

    * `Expression:` `sunset` `15` (15 минут после заката)

    * `Payload:` `{"value": true}`

    * `Topic:` `control/facade_light/set`

  • Расписание "Facade OFF":
  • * `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'

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

    Пример: Управление отоплением. * Расписание в 9:00 отправляет команду термостату: `set temperature to 18°C`.

    * Расписание в 18:00 отправляет команду: `set temperature to 22°C`.

    * Проблема: Если в 10:00 утра пользователь вручную выставит 23°C, его настройка будет работать до 18:00, когда сработает второе расписание и снова изменит температуру, игнорируя желание пользователя.

    1. В `flow context` хранится переменная `system.mode` (например, `home`, `away`, `night`, `vacation`).

    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' позволяет временно блокировать прохождение сообщений. Это идеальный способ деактивировать расписания без их удаления.

    Практический кейс: режим "Отпуск" Задача: Когда система переведена в режим «Отпуск», все бытовые сценарии (утренний, вечерний) не должны срабатывать, но сценарий имитации присутствия должен работать. Решение:
  • State Machine: Состояние системы `flow.system_mode` устанавливается в `vacation`.
  • Scheduler: Узел `cron-plus` продолжает генерировать события «Утро» в 7:00, как обычно.
  • Gate: Между `cron-plus` и логикой утреннего сценария ставится узел `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', который позволяет эффективно работать с массивами данных и множественными ответами от устройств.