SCN-CLIMATE-001: Поддержание температуры (термостат с гистерезисом)
Введение в гистерезис: Ключ к стабильному климат-контролю
ведение в гистерезис: Ключ к стабильному климат-контролю
В предыдущем уроке мы детально разобрали проблему «дребезга» (flapping), возникающую при использовании простой пороговой логики. Напомним, что это приводит к ускоренному износу оборудования, избыточной нагрузке на систему и снижению комфорта.
Решением этой проблемы является гистерезис.
> 💡 Подсказка: Использование гистерезиса значительно продлевает срок службы реле и контакторов, управляющих нагревательными или охлаждающими элементами, за счет сокращения количества циклов включения/выключения.
📋 Ключевые понятия:
- Гистерезис (от греч. "запаздывание") — это свойство системы, при котором её текущее состояние зависит от предыдущих состояний. В контексте климат-контроля это разница между температурой включения и температурой выключения оборудования.
- Целевая температура (Setpoint) — желаемое значение температуры, которое должна поддерживать система.
- Тепловая инерция — задержка реакции среды на включение/выключение источника тепла.
Вместо одного порога, гистерезис вводит два: верхний и нижний. Логика работы меняется:
Между этими двумя порогами образуется "мертвая зона", в которой контроллер не предпринимает никаких действий, позволяя температуре естественно колебаться.
| Критерий | Простая пороговая логика (НЕПРАВИЛЬНО) | Логика с гистерезисом (ПРАВИЛЬНО) |
| :--- | :--- | :--- |
| Уставка | 22.0°C | 22.0°C |
| Гистерезис | 0°C | 1.0°C |
| Порог включения | <= 22.0°C | <= 21.5°C (Уставка - Гистерезис / 2) |
| Порог выключения | > 22.0°C | >= 22.5°C (Уставка + Гистерезис / 2) |
| Поведение при 21.8°C | Включить -> Выключить -> Включить... (дребезг) | Ничего не делать (если был выключен) |
| Износ реле | Очень высокий | Низкий |
Лучшая аналогия для понимания — работа обычного бытового холодильника. Вы задаете желаемую температуру, например, +5°C. Компрессор включается, только когда температура внутри поднимется до +7°C (верхний порог), и работает до тех пор, пока не охладит камеру до +3°C (нижний порог).
Варианты расчета порогов: Симметричный и Асимметричный гистерезис
В зависимости от задачи, "мертвая зона" может распределяться относительно целевой температуры по-разному. Различают два основных подхода программирования логики:
Формула включения:* `Setpoint - (Hysteresis / 2)`
Формула выключения:* `Setpoint + (Hysteresis / 2)`
Формула включения:* `Setpoint - cold_tolerance`
Формула выключения:* `Setpoint + hot_tolerance`
> ⚠️ Зачем нужна асимметрия? Если 22°C — это абсолютный минимум комфорта для пользователя, вы ставите уставку 22°C, `cold_tolerance` = 0.0 (не допускаем падения ниже), а `hot_tolerance` = 1.0 (греем до 23°C и выключаем). Это гарантирует, что температура никогда не упадет до дискомфортных значений.
Применение в освещении: Решение проблемы заката
Хотя мы рассматриваем климат, принцип гистерезиса универсален. В уроке M04 мы упоминали "дребезг" уличного света при проплывающих облаках на закате.
- Проблема: Порог включения 50 лк. Освещенность колеблется 51-49-51 лк. Свет мигает.
- Решение: Включаем свет при `< 50 лк`, а выключаем при `> 150 лк`. Мертвая зона в 100 лк гарантирует, что случайное изменение яркости (фары машины, облако) не вызовет ложных срабатываний.
Выбор величины гистерезиса в зависимости от типа оборудования
Ширина мертвой зоны и допуски должны соотноситься с тепловой инерцией вашей системы:
- Инфракрасные панели, тепловентиляторы (низкая инерция):
- Классические радиаторы, электроконвекторы (средняя инерция):
- Теплый пол в стяжке (очень высокая инерция): Плита отдает тепло часами (эффект перерегулирования/overshoot).
🛠 Практическое мини-задание
Давайте наложим теорию на практические цифры до того, как перейдем к написанию кода.
Сценарий: Вы настраиваете обогреватель типа "конвектор" в спальне. Идеальная температура для сна клиента: 21.5°C. Клиент сильно чувствует холод, если температура падает даже незначительно, но спокойно переносит легкий перегрев.Вы применяете асимметричный гистерезис: `cold_tolerance` = 0.2°C, а `hot_tolerance` = 0.8°C.
Вычислите ожидаемое поведение системы (ответы раскройте ниже):> 💡 Решение и проверка себя:
> 1. Температура включения: `21.5°C - 0.2°C = ` 21.3°C
> 2. Температура выключения: `21.5°C + 0.8°C = ` 22.3°C
> 3. Общая ширина рабочей зоны: `0.2°C + 0.8°C = ` 1.0°C
>
> Итог: Комната всегда будет находиться в диапазоне 21.3°C - 22.3°C, реле не будет нагружено частыми стартами, а клиент не замерзнет.
Проектирование логики термостата в Node-RED
роектирование логики термостата в Node-RED
Перед тем как собирать поток, необходимо четко спроектировать его логику. Наш термостат будет оперировать тремя основными переменными, которые должны быть доступны внутри потока, а также обрабатывать входящие данные от датчика.
Входные данные и состояние
Водяные теплые полы (высокая инертность):* 0.5 – 1.0 °C.
Электрические конвекторы/радиаторы (средняя инертность):* 1.0 – 2.0 °C.
> ⚠️ Внимание: Сохранение состояния в контексте, который очищается при перезапуске (memory-only context), приведет к некорректной работе логики (потере знания о том, включено ли реле) после перезагрузки контроллера или деплоя Node-RED. В рабочих проектах используйте файловый контекст (file-backed context) для критичных сценариев, что настроено по умолчанию на платформе HI. Обращение в коде выглядит так: `flow.get("is_heating", "file")`.
Алгоритм принятия решения
Алгоритм будет реализован в одном узле `function`. На каждом его запуске (при получении нового MQTT-сообщения с температурой) логика пройдет через следующие этапы:
* `upper_threshold = setpoint + (hysteresis / 2)`
* `lower_threshold = setpoint - (hysteresis / 2)`
* ЕСЛИ `current_temp` упала ниже `lower_threshold` И нагреватель сейчас выключен (`is_heating === false`):
* Решение: Включить нагреватель.
* Обновить состояние: `is_heating = true`.
* ИНАЧЕ ЕСЛИ `current_temp` поднялась выше `upper_threshold` И нагреватель сейчас включен (`is_heating === true`):
* Решение: Выключить нагреватель.
* Обновить состояние: `is_heating = false`.
* ИНАЧЕ:
* Температура находится в норме или внутри гистерезиса. Команду отправлять не нужно.
Пример реализации в коде (Узел Function)
На основе спроектированного алгоритма мы напишем следующий JavaScript код для узла Function (подробно разберем его применение в следующем разделе):
// Извлекаем температуру с датчика, преобразуем в число (float)
let current_temp = parseFloat(msg.payload);
// 1. Валидация: если пришло не число, прерываем выполнение
if (isNaN(current_temp)) {
node.warn("Получена некорректная температура: " + msg.payload);
return null;
}
// 2. Инициализация переменных из контекста с дефолтными значениями
let setpoint = flow.get("setpoint") || 22.0;
let hysteresis = flow.get("hysteresis") || 1.0;
// Состояние реле: если переменной нет, считаем, что выключено
let is_heating = flow.get("is_heating") || false;
// 3. Расчет порогов
let half_hyst = hysteresis / 2;
let upper_threshold = setpoint + half_hyst;
let lower_threshold = setpoint - half_hyst;
// 4. Флаг изменения состояния
let state_changed = false;
// Основная логика термостата
if (current_temp <= lower_threshold && !is_heating) {
is_heating = true;
state_changed = true;
} else if (current_temp >= upper_threshold && is_heating) {
is_heating = false;
state_changed = true;
}
// 5. Визуализация статуса узла в Node-RED
let status_text = `T: ${current_temp.toFixed(1)}°C | Уст: ${setpoint.toFixed(1)}°C`;
node.status({
fill: is_heating ? "red" : "grey",
shape: "dot",
text: (is_heating ? "НАГРЕВ | " : "ОЖИДАНИЕ | ") + status_text
});
// 6. Формирование исходящего сообщения или блокировка потока
if (state_changed) {
// Сохраняем новое состояние в контекст потока
flow.set("is_heating", is_heating);
// Для нашего реле 1 - включить, 0 - выключить
msg.payload = is_heating ? 1 : 0;
return msg;
} else {
// Если состояние не изменилось, останавливаем поток (ничего не отправляем)
return null;
}
Практическое мини-задание (Mental Check)
> 💡 Проверьте себя перед сборкой:
> Представьте, что вы задали уставку (`setpoint`) = 23.0°C, а гистерезис (`hysteresis`) = 2.0°C. Сейчас в помещении тепло, нагреватель выключен. Вы открываете окно, и температура начинает падать.
>
> Вопрос 1: При какой конкретно температуре термостат подаст команду на включение?
> Вопрос 2: После включения вы закрыли окно, комната греется. При какой температуре термостат подаст команду на выключение?
> Вопрос 3: Что произойдет при получении температуры 22.5°C?
>
> Ожидаемые ответы:
> 1. Включение произойдет при `<= 22.0°C` (23.0 - (2.0/2)).
> 2. Выключение произойдет при `>= 24.0°C` (23.0 + (2.0/2)).
> 3. При 22.5°C система находится внутри гистерезиса. Скрипт проверит состояние (если греем — продолжаем греть, если выключено — останется выключенным), обновит статус под узлом и вернет `null` (команда на реле не отправится). Состояние не изменится.
Такая логика гарантирует, что команда на переключение отправится строго в момент пересечения порогов, аппаратура реле будет защищена от частого щелканья, а радиоэфир (или шина платформы) не будет засоряться дублирующими сообщениями каждую секунду поступления данных с датчика.
Практическая реализация: Сборка потока термостата
рактическая реализация: Сборка потока термостата
Теперь, имея четкий алгоритм, мы можем собрать рабочий поток `FLOW-CLIMATE-THERMOSTAT-001` в среде Node-RED на контроллере HI (или совместимом, например, Wiren Board).
Структура потока
Поток будет состоять из следующих основных узлов:
┌────────────────┐ ┌───────────────────────────┐ ┌──────────┐
(MQTT In)──┤ MQTT In ├─►───-│ Function: Термостат ├─►───-┤ MQTT Out │───(To Relay)
│ hi/temp/room1 │ │ (Логика с гистерезисом) │ │ hi/relay/1/set │
└────────────────┘ └───────────┬───────────────┘ └──────────┘
│
│ (Визуальный статус)
▼
┌───────────┐
│ node.status() │
└───────────┘
Дополнительно, для отладки, мы обязательно подключим узел `Debug` к выходу узла `Function`, а также несколько узлов `Inject` для симуляции датчика.
Конфигурация узлов
* Сервер: Выберите MQTT-брокер вашего контроллера (обычно `localhost:1883`).
* Топик: Укажите топик, из которого приходят данные с датчика температуры. Например, для стандартного датчика WAGO, 1-Wire или Zigbee это может быть `telemetry/office/room1/temperature`.
* Вывод: В поле `Output` выберите параметр `a parsed JSON object` (Разобранный объект JSON). Мы предполагаем, что датчик отправляет данные в стандартизированном формате (паттерн "Контракт сообщения"):
{
"value": 21.8,
"source": "temp-sensor-office-1",
"ts": 1678886400000,
"unit": "°C"
}
Примечание:* Если ваш брокер отдает только "чистое" значение (payload вида `21.8`), вам потребуется скорректировать логику в узле `function` (убрать `.value`).
Это сердце нашего сценария. Скопируйте следующий расширенный код в редактор узла.
> 💡 Подсказка: Используйте `node.warn()` внутри узла `function` для вывода отладочных сообщений в боковую панель Debug. Это позволяет отслеживать переходные состояния без необходимости ставить лишние узлы.
// SCN-CLIMATE-001: Логика термостата с гистерезисом
// --- НАСТРОЙКИ ---
// Целевая температура (Уставка / Setpoint).
// Забираем из контекста (если задано через UI/Дашборд), иначе дефолт 22.0.
const SETPOINT = flow.get('target_temp') || 22.0;
// Гистерезис - "мертвая зона". Например, 1.0°C означает зону в ±0.5°C от уставки.
const HYSTERESIS = flow.get('target_hysteresis') || 1.0;
// -----------------
// 1. ИНИЦИАЛИЗАЦИЯ
// Получаем текущее состояние нагревателя из контекста потока.
// Если его нет (первый запуск), считаем, что он выключен (false).
let isHeating = flow.get('thermostat_is_heating') || false;
// Сохраняем настройки в контекст для отладки.
flow.set('thermostat_setpoint', SETPOINT);
flow.set('thermostat_hysteresis', HYSTERESIS);
// 2. ПОЛУЧЕНИЕ И ВАЛИДАЦИЯ ДАННЫХ
// Ожидаем, что температура приходит в msg.payload.value (JSON формат)
const currentTemp = parseFloat(msg.payload.value);
// Проверяем, что получили корректное число.
if (isNaN(currentTemp)) {
node.status({ fill: "red", shape: "dot", text: "Ошибка: неверные данные" });
node.error("Получено нечисловое значение температуры.", msg);
return null; // Прерываем выполнение
}
// 3. РАСЧЕТ ПОРОГОВ
const lowerThreshold = SETPOINT - (HYSTERESIS / 2); // Порог включения (21.5°C)
const upperThreshold = SETPOINT + (HYSTERESIS / 2); // Порог выключения (22.5°C)
// 4. ОСНОВНАЯ ЛОГИКА
let command = null; // Команда для реле (обычно строка "1" или "0")
let stateChanged = false; // Флаг изменения состояния
if (currentTemp < lowerThreshold && isHeating === false) {
// Температура упала ниже нижнего порога, а нагреватель выключен. ВКЛЮЧАЕМ.
command = "1"; // Строковый формат надежнее для большинства MQTT-драйверов ПЛК
isHeating = true;
stateChanged = true;
node.warn(`ВКЛЮЧЕНИЕ НАГРЕВА: Temp (${currentTemp}°C) < Lower (${lowerThreshold}°C)`);
} else if (currentTemp >= upperThreshold && isHeating === true) {
// Температура поднялась выше верхнего порога, а нагреватель включен. ВЫКЛЮЧАЕМ.
command = "0";
isHeating = false;
stateChanged = true;
node.warn(`ВЫКЛЮЧЕНИЕ НАГРЕВА: Temp (${currentTemp}°C) >= Upper (${upperThreshold}°C)`);
}
// 5. СОХРАНЕНИЕ СОСТОЯНИЯ И ВИЗУАЛИЗАЦИЯ
// Обновляем состояние в персистентном контексте
flow.set('thermostat_is_heating', isHeating);
// Обновляем визуальный статус узла для быстрой диагностики
let statusText = `Temp: ${currentTemp.toFixed(1)}°C | Set: ${SETPOINT.toFixed(1)}°C`;
node.status({
fill: isHeating ? "red" : "blue",
shape: "dot",
text: (isHeating ? "[ON] " : "[OFF] ") + statusText
});
// 6. ФОРМИРОВАНИЕ КОМАНДЫ
// Отправляем сообщение дальше, только если состояние изменилось.
// Это предотвращает флуд в MQTT-брокере и бережет ресурс реле (особенно электромагнитного).
if (stateChanged) {
msg.payload = command;
return msg;
} else {
// Состояние в зоне гистерезиса или не пересекло пороги, ничего не отправляем в реле.
return null;
}
* Сервер: Тот же брокер `localhost:1883`.
* Топик: Укажите командный топик для реле (нагревателя). На платформе Wiren Board для релейного модуля это выглядит как: `/devices/wb-mr6c_25/controls/K1/on`. Убедитесь, что реле, используемое для нагревателя, рассчитано на соответствующую нагрузку (ток) или подключено через контактор/твердотельное реле (SSR).
* QoS: `1` (Обязательная доставка).
* Retain: `false` (Командные топики обычно не должны быть Retained, кроме специфических настроек драйвера).
Отладка и тестирование (Мини-задание)
Загружать логику сразу на реальное "железо", не проверив её, — плохая практика. Проведем стендовое тестирование.
* Inject 1 (Холодно): `{"value": 20.5}`
* Inject 2 (Нормально): `{"value": 21.8}`
* Inject 3 (Жарко): `{"value": 22.8}`
- Шаг 1: Нажмите Inject 1 (20.5).
- Шаг 2: Нажмите Inject 2 (21.8).
- Шаг 3: Нажмите Inject 3 (22.8).
После успешного выполнения этого теста поток термостата готов к интеграции с реальными MQTT-устройствами. Заданная температура будет поддерживаться автоматически с бережным отношением к ресурсу оборудования.
Резюме и лучшие практики
езюме и лучшие практики
В этом уроке мы разработали один из самых фундаментальных и востребованных сценариев автоматизации — термостат с гистерезисом. Мы разобрали, почему простая пороговая логика неприменима на практике и как «мертвая зона», создаваемая гистерезисом, решает проблему дребезга оборудования (частого переключения реле).
Ключевые концепции, которые мы освоили:
- Гистерезис является обязательным элементом для любых систем управления с инертными процессами (нагрев, охлаждение, давление, уровень жидкости). На практике значение гистерезиса подбирается под тип оборудования: `±0.1°C` — `±0.2°C` для быстрых инфракрасных обогревателей, `±0.5°C` для классических радиаторов и `±1.0°C` (и более) для инерционного теплого пола.
- Состояние (`state`) должно храниться в персистентном контексте потока (`flow context`). Использование команд `flow.get()` и `flow.set()` гарантирует, что программа «помнит», греем мы сейчас или остываем.
- Разделение логики и оборудования: Мы использовали MQTT как универсальный транспорт для данных. Логика термостата не имеет прямого доступа к физическим контактам оборудования; она лишь получает данные из одного топика (`sensor/temperature`) и отправляет команды в другой (`relay/heater/set`). Это позволяет легко заменить датчик или релейный модуль без изменения самого сценария.
Чек-лист проверки надёжности термостата
Перед тем как внедрять подобный узел в реальную (продуктовую) среду умного дома, убедитесь, что он соответствует следующим критериям:
- [ ] Защита от потери данных датчика: Алгоритм не реагирует на пустые значения (`null` или `NaN`), которые могут прийти при обрыве связи с датчиком.
- [ ] Защита от дребезга: Размер гистерезиса больше, чем погрешность самого датчика температуры (например, если датчик шумит на `±0.2°C`, гистерезис должен быть строго больше `0.2°C`).
- [ ] Восстановление после сбоя: При инициализации (после перезагрузки) узел корректно считывает историческое состояние реле из контекста или делает принудительный запрос статуса (MQTT Retain).
Мини-задание: Симуляция работы термостата
Чтобы убедиться в правильности работы логики, проведите ручное тестирование потока. Используйте узлы `Inject` или программу MQTT Explorer для отправки тестовых значений.
Условия: `SETPOINT = 22.0`, `HYSTERESIS = 0.5`.Верхний порог отключения: `22.5°C`. Нижний порог включения: `21.5°C`.
План тестирования (отправляйте значения по очереди):Векторы развития сценария
Этот сценарий является надежным фундаментом для более сложных систем климат-контроля и логичным продолжением темы практических сценариев после изучения освещения (урок COURSE-07-M06-L01). Его можно абстрагировать в Subflow и развивать дальше:
- Добавление режимов «Нагрев» / «Охлаждение»: Добавить переключатель логики (реверс) для работы не с обогревателем, а с кондиционером.
- Работа по расписанию: Интегрировать сценарий с планировщиком (например, `node-red-contrib-cron-plus`) для автоматической установки разных целевых температур днём, ночью и в периоды отсутствия дома.
- Интеграция с датчиками состояния окон и присутствия: Добавить override-логику: автоматически переопределять `SETPOINT` на минимальный порог (например, 7°C для защиты от промерзания) или полностью блокировать отправку `ON`, если в помещении открыто окно.
- ПИД-регулирование: Для сложных систем с плавной регулировкой (например, сервоприводы термоголовок) перейти от релейного управления (вкл/выкл) к расчету процента открытия клапана.
> 💡 Информация: В следующем уроке мы расширим этот базовый сценарий, добавив возможность работы по недельному расписанию и удобное ручное управление через веб-интерфейс, используя узлы `node-red-dashboard`.
Что дальше?
Убедитесь, что вы успешно выполнили мини-задание и полностью поняли логику сохранения состояний (`flow.get` / `flow.set`) внутри узла `function`. Поэкспериментируйте с граничными значениями: отправьте текст вместо числа или экстремально высокие значения, чтобы продумать, как узел должен реагировать на аномалии. Подготовьтесь к следующей лабораторной работе, где мы будем объединять этот логический блок с интерфейсом пользователя.