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

Практика: Сценарий управления отоплением с гистерезисом

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

Введение: Постановка задачи для сценария отопления

ведение: Постановка задачи для сценария отопления

Целью данного урока является разработка полнофункционального и, что наиболее важно, стабильного сценария управления системой отопления на базе контроллера HI. Мы перейдем от теоретических концепций, изученных в предыдущих уроках этого модуля, к созданию реального инженерного решения, готового к внедрению на объекте.

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

Решением проблемы дребезга является внедрение гистерезиса. Мы откажемся от одной жесткой точки уставки и создадим программируемую «мертвую зону». Вводя два разных порога (нижний для включения обогревателя и верхний для выключения), мы гарантируем, что система после включения отработает полный цикл подогрева помещения и не станет дергать контактор из-за падения температуры на доли градуса после выключения.

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

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

Проектирование логики: Уставка, Гистерезис и Пороги

роектирование логики: Уставка, Гистерезис и Пороги

Перед тем как приступить к сборке потока в Node-RED, необходимо четко спроектировать его логику. Правильное проектирование — залог простоты реализации и дальнейшей поддержки сценария.

Определение ключевых переменных

Для нашего сценария потребуются три основные переменные:

  • Setpoint (Уставка): Целевая температура, которую мы хотим поддерживать в помещении. Это значение должно быть легко изменяемым, в идеале — через пользовательский интерфейс или систему верхнего уровня.
  • * Пример: `22.0` (°C)

  • Hysteresis (Гистерезис): Величина «мертвой зоны» или дельты. Это значение определяет, насколько температура должна упасть ниже уставки, прежде чем включится обогрев.
  • * Пример: `1.0` (°C)

  • Temperature (Температура): Текущее значение температуры в помещении, получаемое от физического датчика (например, по MQTT или Modbus).
  • Расчет порогов срабатывания

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

    `Lower_Threshold = Setpoint - Hysteresis`

    * Пример: `22.0 - 1.0 = 21.0` °C

    `Upper_Threshold = Setpoint`

    * Пример: `22.0` °C

    > 💡 Подсказка: Для удобства в реальных проектах уставку (Setpoint) и гистерезис (Hysteresis) лучше выносить в глобальные переменные, персистентный контекст или в отдельные MQTT-топики. Это позволит менять их "на лету" через панель управления (Dashboard) или мобильное приложение, не редактируя и не перезапуская сам сценарий Node-RED.

    Описание логики работы и управление состоянием

    Теперь опишем полный алгоритм работы системы. Ключевой элемент здесь — состояние (State), которое показывает, включен (`true`) или выключен (`false`) нагреватель в данный момент. Мы должны хранить это состояние, чтобы избежать отправки лишних дублирующих команд на реле.

    Логика работы при поступлении нового значения температуры:

  • Сравнение с нижним порогом:
  • * ЕСЛИ `Temperature < Lower_Threshold` (например, 20.9 < 21.0)

    * И ЕСЛИ состояние системы сейчас `ВЫКЛЮЧЕНО` (`state == false`)

    * ТО изменить состояние на `ВКЛЮЧЕНО` (`state = true`) и отправить команду на включение нагревателя.

  • Сравнение с верхним порогом:
  • * ЕСЛИ `Temperature >= Upper_Threshold` (например, 22.0 >= 22.0)

    * И ЕСЛИ состояние системы сейчас `ВКЛЮЧЕНО` (`state == true`)

    * ТО изменить состояние на `ВЫКЛЮЧЕНО` (`state = false`) и отправить команду на выключение нагревателя.

  • "Мертвая зона" (Гистерезис):
  • * ЕСЛИ `Lower_Threshold <= Temperature < Upper_Threshold` (например, температура находится в диапазоне `21.0, 22.0)`)

    * ТО ничего не делать. Система переходит в режим ожидания и сохраняет свое текущее состояние.

    > 💡 Напоминание: Детальную физику процесса и теорию о том, почему возникает «флаппинг» (дребезг реле) без использования мертвых зон, мы подробно разбирали в [базовом уроке по теории состояний. В текущем уроке мы опускаем теоретическую часть и фокусируемся исключительно на программной реализации этого поведения в Node-RED.

    Практический подход к хранению состояния

    Для хранения переменной `state` мы будем использовать контекст узла (node context). В Node-RED существует три уровня контекста (Node, Flow, Global), но поскольку текущее состояние нагревателя (включен/выключен) необходимо отслеживать только внутри конкретного узла `function`, вычисляющего пороги, уровня Node будет достаточно. Это позволяет скрипту "помнить" свое состояние между поступлениями новых данных от датчика температуры, не делясь этой информацией с остальными частями потока и не расходуя глобальную память.

    Практика: Сборка потока в Node-RED

    рактика: Сборка потока в Node-RED

    Перейдем к непосредственной реализации спроектированной логики на контроллере HI.

    Обзор необходимых узлов

    Для сборки нашего потока мы будем использовать стандартный набор узлов, доступных в Node-RED.

    
    

    Реализация гистерезиса в узле Function

    Теперь напишем JavaScript-код, который будет выполнять всю основную работу. Этот код будет размещен внутри узла `function`, который мы создали ранее.

    Инициализация переменных и работа с контекстом

    Первым делом, внутри функции нам нужно определить наши константы (уставку и гистерезис) и получить текущее значение температуры из входящего сообщения. Также мы будем активно использовать контекст узла (node context) для хранения состояния нагревателя между вызовами функции.

    javascript

    // 1. Определение параметров сценария из контекста

    // Считываем уставку и гистерезис из контекста потока (flow context).

    // Это позволяет менять их "на лету", не редактируя код.

    // Если переменные не заданы, используются значения по умолчанию.

    const setpoint = flow.get('heating_setpoint') || 22.0;

    const hysteresis = flow.get('heating_hysteresis') || 1.0;

    // Рассчитываем пороги

    const lowerThreshold = setpoint - hysteresis;

    const upperThreshold = setpoint;

    // 2. Получение и валидация входящих данных

    // Датчик присылает температуру в виде строки, преобразуем ее в число

    const currentTemp = parseFloat(msg.payload);

    // Проверяем, что получили корректное число.

    // Если нет - выводим ошибку в лог и останавливаем выполнение.

    if (isNaN(currentTemp)) {

    node.error("Получено нечисловое значение температуры: " + msg.payload, msg);

    node.status({fill:"red", shape:"dot", text:"Ошибка: неверные данные"});

    return null; // Прекратить обработку

    }

    // 3. Работа с состоянием

    // Получаем ТЕКУЩЕЕ состояние нагревателя из контекста узла.

    // Если оно не задано (первый запуск), считаем, что нагреватель выключен (false).

    let isHeating = context.get('heatingState') || false;

    // Переменная для новой команды. По умолчанию - не меняем состояние.

    let command = null;

    // 4. Основная логика гистерезиса

    if (currentTemp < lowerThreshold) {

    // Температура упала ниже нижнего порога

    if (isHeating === false) {

    // И нагреватель был выключен...

    command = 1; // ...формируем команду на ВКЛЮЧЕНИЕ

    isHeating = true; // и обновляем наше внутреннее состояние

    }

    // Если нагреватель уже был включен, ничего не делаем

    } else if (currentTemp >= upperThreshold) {

    // Температура достигла или превысила верхний порог

    if (isHeating === true) {

    // И нагреватель был включен...

    command = 0; // ...формируем команду на ВЫКЛЮЧЕНИЕ

    isHeating = false; // и обновляем наше внутреннее состояние

    }

    // Если нагреватель уже был выключен, ничего не делаем

    }

    // Если температура находится в "мертвой зоне", блок if/else if не сработает,

    // и переменная 'command' останется 'null'.

    // 5. Отправка команды и обновление статуса

    if (command !== null) {

    // Если была сформирована новая команда (0 или 1)...

    // Сохраняем новое состояние в контексте узла для следующего вызова

    context.set('heatingState', isHeating);

    // Формируем исходящее сообщение для отправки на реле

    msg.payload = command;

    // Обновляем визуальный статус узла для удобства отладки

    if(isHeating) {

    node.status({fill:"red", shape:"dot", text:"ВКЛ: " + currentTemp + "°C"});

    } else {

    node.status({fill:"green", shape:"dot", text:"ВЫКЛ: " + currentTemp + "°C"});

    }

    // Отправляем сообщение дальше по потоку (на узел 'rbe' и затем 'mqtt out')

    return msg;

    } else {

    // Если команда не изменилась, просто обновляем статус для информации

    const statusText = isHeating ? "ВКЛ: " : "ВЫКЛ: ";

    node.status({shape:"dot", fill: isHeating ? "red" : "green", text: statusText + currentTemp + "°C"});

    // И ничего не отправляем дальше (возвращаем null)

    return null;

    }

    ```

    Разбор кода:

    Теперь скопируйте этот код в ваш узел `function`. Сценарий готов к работе.

    ---

    Отказоустойчивость: Обработка нештатных ситуаций

    тказоустойчивость: Обработка нештатных ситуаций

    Хотя использование программного гистерезиса уже защитило нас от частого переключения состояний (флаппинга) на границе целевой температуры (подробнее о физике этого процесса мы говорили в базовом уроке по устранению дребезга или реализации watchdog-таймера (как мы сделали для отслеживания `safe-