SCN-CLIMATE-011: Интеграция с прогнозом погоды для управления отоплением/поливом
Введение в проактивное управление климатом
В предыдущих уроках мы рассматривали преимущественно реактивные сценарии управления климатом. Система реагирует на уже произошедшие события: температура в комнате опустилась ниже уставки — включается отопление; концентрация CO2 превысила норму — запускается вентиляция. Такой подход эффективен для систем с малой инерционностью, но он не всегда оптимален с точки зрения комфорта и энергосбережения.
Проактивное (упреждающее) управление — это парадигма, в которой система автоматизации принимает решения на основе прогнозируемых, а не текущих данных. Вместо того чтобы ждать, пока в доме станет холодно из-за резкого похолодания на улице, система может начать прогрев заранее, опираясь на прогноз погоды.Ключевые преимущества проактивного подхода:
* Отопление: Зная, что днем ожидается солнечная погода и потепление, система может снизить интенсивность утреннего прогрева, позволяя "догреть" помещение естественным теплом от солнца.
* Полив: Автоматическая отмена полива газона, если в ближайшие часы прогнозируется дождь, напрямую экономит воду и электроэнергию на работу насоса.
Источники данных и ключевые параметры
Для реализации проактивных сценариев необходим надежный источник данных о будущем состоянии окружающей среды. Таким источником являются API (Application Programming Interface) погодных сервисов.
> 📋 Ключевые понятия:
> * API (Application Programming Interface): Программный интерфейс, который позволяет одному приложению (в нашем случае, Node-RED на контроллере HI) получать данные из другого приложения (погодного сервиса) в стандартизированном формате.
> * JSON (JavaScript Object Notation): Текстовый формат обмена данными, который легко читается как человеком, так и машиной. Большинство современных API используют JSON для передачи данных.
Наиболее популярные сервисы, предоставляющие погодные API:
- OpenWeatherMap
- Weather API
- Tomorrow.io
В рамках этого урока мы будем использовать OpenWeatherMap как один из наиболее доступных и хорошо документированных сервисов.
При работе с погодными API нас интересуют следующие ключевые параметры:
| Параметр | Описание | Пример применения |
| ------------------- | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- |
| Текущая температура | `main.temp`: Фактическая температура воздуха на данный момент. | Расчет дельты для упреждающего прогрева. |
| Прогноз температуры | `daily.temp.day`, `hourly.temp`: Прогноз температуры на ближайшие часы или дни. | Запуск отопления до наступления похолодания. |
| Прогноз осадков | `daily.pop` (вероятность), `daily.rain` (объем): Прогноз вероятности и интенсивности осадков. | Отмена автоматического полива. |
| Влажность воздуха | `main.humidity`: Текущая относительная влажность. | Корректировка работы систем вентиляции и увлажнения. |
| Скорость ветра | `wind.speed`: Может влиять на теплопотери здания. | Повышение уставки отопления в ветреную погоду. |
| Облачность | `clouds.all`: Процент покрытия неба облаками. | Учет пассивного солнечного обогрева. |
Основная идея состоит в том, чтобы периодически запрашивать эти данные, анализировать их и корректировать работу стандартных реактивных сценариев (например, термостата, рассмотренного в `SCN-CLIMATE-001`).
---
Настройка API и получение данных в Node-RED
Первый практический шаг — настроить получение данных из погодного сервиса. Мы будем использовать узел `http request` для отправки запросов к API OpenWeatherMap и узел `function` для обработки ответа.
Шаг 1: Регистрация и получение API-ключа
> ⚠️ Внимание: Никогда не храните API-ключ в открытом виде внутри узлов Node-RED. Это небезопасно. API-ключ является вашим персональным идентификатором, и его компрометация может привести к блокировке доступа или исчерпанию лимитов. Для безопасного хранения используйте переменные окружения контроллера или файл `credentials`, как это было рассмотрено в курсе по Production Readiness. В примерах ниже мы будем использовать placeholder `YOUR_API_KEY` для наглядности.
Шаг 2: Создание потока для запроса погоды
Создадим поток, который будет один раз в час запрашивать прогноз погоды.
Flow Diagram (ASCII):[Inject: раз в час] --> [http request: получить погоду] --> [function: обработать и сохранить] --> [debug]
|
+----(ошибка)----> [catch] ----> [log/notify]
* Payload: `timestamp`
* Repeat: `interval`, `every 1 hour`
* Name: `Запрос погоды (1 раз в час)`
* Method: `GET`
* URL: Для получения прогноза мы будем использовать "One Call API 3.0". URL имеет следующий формат:
https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={lon}&exclude={part}&appid={API key}&units=metric&lang=ru
- `{lat}` и `{lon}`: Широта и долгота вашего объекта. Их можно получить, например, в Яндекс.Картах.
- `{API key}`: Ваш API-ключ.
- `units=metric`: Получать данные в метрической системе (°C, м/с).
- `lang=ru`: Получать текстовые описания (например, "небольшой дождь") на русском языке.
Пример готового URL (для Москвы):
`https://api.openweathermap.org/data/3.0/onecall?lat=55.7522&lon=37.6156&exclude=minutely,alerts&appid=YOUR_API_KEY&units=metric&lang=ru`
* Return: `a parsed JSON object`
* Name: `GET OpenWeatherMap API`
Этот узел будет извлекать нужные нам данные из огромного JSON-ответа и сохранять их в глобальный контекст для использования другими сценариями.
* Name: `Сохранить прогноз в global`
* Код:
// Пример структуры входящего msg.payload от API
// {
// "lat": 55.7522,
// "lon": 37.6156,
// "current": { "temp": 15.5, "humidity": 60, ... },
// "hourly": [ { "dt": 1678886400, "temp": 16.1, "pop": 0.1 }, ... ],
// "daily": [ { "dt": 1678886400, "temp": { "day": 18.2 }, "pop": 0.5, ... }, ... ]
// }
// Валидация ответа
if (!msg.payload || !msg.payload.current || !msg.payload.daily) {
node.error("Получен некорректный ответ от API погоды", msg);
node.status({ fill: "red", shape: "dot", text: "Invalid API response" });
return null;
}
const forecast = {
// Текущие данные
current: {
temp: msg.payload.current.temp,
humidity: msg.payload.current.humidity,
wind_speed: msg.payload.current.wind_speed
},
// Прогноз на сегодня (первый элемент массива daily)
today: {
temp_day: msg.payload.daily[0].temp.day,
temp_night: msg.payload.daily[0].temp.night,
pop: msg.payload.daily[0].pop, // Вероятность осадков от 0 до 1
summary: msg.payload.daily[0].summary
},
// Прогноз на завтра (второй элемент массива daily)
tomorrow: {
temp_day: msg.payload.daily[1].temp.day,
temp_night: msg.payload.daily[1].temp.night,
pop: msg.payload.daily[1].pop
},
// Данные обновлены
last_update: Date.now()
};
// Сохраняем обработанные данные в глобальный контекст
global.set('weather_forecast', forecast);
node.status({ fill: "green", shape: "dot", text: "OK: " + forecast.current.temp + "°C" });
// Можно передать данные дальше для отладки
msg.payload = forecast;
return msg;
Теперь, после выполнения этого потока, любой другой сценарий в Node-RED может получить доступ к актуальному прогнозу с помощью `global.get('weather_forecast')`.
---
Логика упреждающего управления отоплением
Рассмотрим применение полученных данных для системы отопления с высокой инерционностью, например, для водяного теплого пола. Классический термостат включит котел, когда пол и воздух уже остыли. Нам же нужно начать прогрев за 2-4 часа до прогнозируемого пика холода.
Задача: Заблаговременно повысить целевую температуру термостата, если прогноз погоды обещает резкое похолодание.Анализ прогноза и расчет дельты
Логика будет срабатывать параллельно с основным сценарием термостата. Она не будет напрямую управлять котлом, а будет изменять уставку (`setpoint`) для термостата.
- `current_temp` = `weather_forecast.current.temp`
- `forecast_night_temp` = `weather_forecast.today.temp_night`
Пример реализации в Node-RED
Этот поток должен выполняться по расписанию и корректировать уставку в основном потоке управления климатом.
Flow Diagram (ASCII):[Inject: раз в 30 мин] --> [function: Проверить прогноз отопления] --> [switch: Нужен прогрев?] --(Да)--> [function: Сформировать команду] --> [mqtt out: new_setpoint]
Код для узла `function: Проверить прогноз отопления`:
// --- Параметры сценария ---
// Порог разницы температур для активации (°C)
const TEMP_DELTA_THRESHOLD = 10;
// Порог текущей уличной температуры, ниже которого включается логика (°C)
const CURRENT_TEMP_THRESHOLD = 5;
// Часы, в которые анализируется прогноз (вечер)
const START_HOUR = 18;
const END_HOUR = 22;
// Получаем данные из глобального контекста
const forecast = global.get('weather_forecast');
const currentHour = new Date().getHours();
// Проверка валидности данных (рассмотрено в SCN-CLIMATE-008)
if (!forecast || (Date.now() - forecast.last_update > 2 60 60 * 1000)) {
// Данные устарели (нет обновлений > 2 часов)
node.status({ fill: "yellow", shape: "ring", text: "Stale forecast data" });
// Переходим к логике отката (fallback) - ничего не делаем
return null;
}
const currentTemp = forecast.current.temp;
const nightTemp = forecast.today.temp_night;
const tempDelta = currentTemp - nightTemp;
// Проверяем условия для упреждающего прогрева
const needsPreheating =
(currentHour >= START_HOUR && currentHour < END_HOUR) && // Время для анализа
(currentTemp > CURRENT_TEMP_THRESHOLD) && // На улице еще не слишком холодно
(tempDelta >= TEMP_DELTA_THRESHOLD); // Но ожидается резкое похолодание
msg.payload = {
needs_preheating: needsPreheating,
details: {
current_temp: currentTemp,
night_temp: nightTemp,
delta: tempDelta
}
};
if (needsPreheating) {
node.status({ fill: "blue", shape: "dot", text: `Pré-chauffage activé: ΔT=${tempDelta}°C` });
} else {
node.status({ fill: "grey", shape: "dot", text: `Pré-chauffage non requis` });
}
return msg;
Далее узел `switch` проверяет `msg.payload.needs_preheating`. Если `true`, то следующий узел `function` формирует команду на повышение уставки.
Код узла `function: Сформировать команду`:// Текущая уставка для теплого пола, хранится в контексте
let currentSetpoint = flow.get('floor_setpoint') || 24.0;
// На сколько градусов повысить уставку для упреждающего прогрева
const PREHEAT_BOOST = 2.0;
// Формируем сообщение по контракту для нашего сценария термостата
msg.topic = 'commands/heating/floor/setpoint/set';
msg.payload = {
value: currentSetpoint + PREHEAT_BOOST,
source: 'SCN-CLIMATE-011-Preheat',
ts: Date.now(),
meta: {
reason: 'Proactive heating due to forecast'
}
};
return msg;
Это сообщение отправляется в MQTT и перехватывается основным сценарием термостата, который плавно поднимает температуру в помещении еще до наступления холодов.
---
Практика: Сценарий 'Умный полив'
Один из самых очевидных и эффективных сценариев, использующих прогноз погоды, — это автоматизация полива. Главная цель — не тратить воду, если природа сделает это за нас.
Основное правило: Отменить запланированный сеанс полива, если в ближайшие 12 часов прогнозируется дождь со значительной вероятностью.Логика сценария
> 💡 Подсказка: Используйте узел `node-red-contrib-within-time` для дополнительной проверки, что полив не будет случайно запущен в дневную жару (например, с 11:00 до 17:00). Это помогает избежать быстрого испарения влаги и ожогов растений.
Пример реализации в Node-RED
Flow Diagram (ASCII):// Запуск полива по расписанию
[Cron-plus: каждый день в 05:00] --> [switch: Проверить режим дома] --(Дома)--> [function: Проверить прогноз осадков] --> [gate: Блокировать если дождь] --> [function: Открыть клапан на 20 мин] --> [Relay Out: Клапан]
|
+---- (управляется `msg.topic`)
[function: Закрыть клапан] --------------------------+
Код узла `function: Проверить прогноз осадков`:
// --- Параметры сценария ---
// Порог вероятности осадков для отмены полива (0.0 - 1.0)
const PRECIPITATION_PROBABILITY_THRESHOLD = 0.5;
const forecast = global.get('weather_forecast');
// Валидация данных прогноза
if (!forecast || !forecast.today) {
node.warn("Данные прогноза недоступны. Полив будет выполнен по стандартному расписанию.");
// В случае ошибки API, мы не блокируем полив. Это fallback-логика.
// Просто не отправляем команду на блокировку.
return msg;
}
const rainProbability = forecast.today.pop;
const willRain = rainProbability >= PRECIPITATION_PROBABILITY_THRESHOLD;
if (willRain) {
node.status({ fill: "blue", shape: "ring", text: "Полив отменен: прогноз дождя (" + (rainProbability * 100) + "%)" });
// Отправляем команду на закрытие "ворот" (gate)
// Узел simple-gate блокирует прохождение сообщений, если получает topic 'close'
msg.topic = 'close';
} else {
node.status({ fill: "green", shape: "dot", text: "Прогноз ясный, полив разрешен." });
// Отправляем команду на открытие "ворот"
msg.topic = 'open';
}
// Это сообщение пойдет на управляющий вход узла `simple-gate`
return msg;
- Узел `simple-gate` устанавливается после этого узла `function`. Он будет пропускать или блокировать сообщение от `Cron-plus` в зависимости от команды, полученной от `function`.
- Узел `function: Открыть клапан на 20 мин` реализует таймер. Он отправляет команду на включение реле, а через 20 минут — команду на выключение. Это можно сделать с помощью узла `trigger`.
// Сообщение для включения клапана полива, подключенного к реле №10
{
"payload": true,
"topic": "commands/relay/10/set"
}
Этот сценарий можно усложнить, корректируя длительность полива (`duration`) в зависимости от прогнозируемой дневной температуры и количества дней без осадков.
---
Интеграция с глобальными режимами
Проактивные сценарии не должны работать в вакууме. Их необходимо интегрировать с общим состоянием системы, которое определяется глобальными режимами ("Дома", "Нет дома", "Сон", "Отпуск").
> 🔗 Связанный материал: Логика работы с режимами, их установка и использование в сценариях подробно рассмотрены в уроке `SCN-ENERGY-001: Отключение розеток в режиме 'Away' или 'Night'`.
Правильная иерархия приоритетов выглядит так:
Глобальный режим > Проактивный сценарий > Реактивный сценарийЭто означает, что решение, принятое на уровне глобального режима, всегда имеет наивысший приоритет.
Примеры интеграции:
* Проблема: Вы уехали в отпуск, а система продолжает поливать газон каждый день, даже если идут дожди, расходуя воду.
* Решение: В самом начале потока 'Умный полив', сразу после `inject` или `cron`, ставится узел `switch`, который проверяет значение `global.get('Home.Mode')`. Если режим равен "Отпуск" (`Vacation`), поток прерывается.
Пример узла `switch`:
* Property: `global.Home.Mode` (тип `global`)
* Правило 1: `!=` (string) `Vacation` -> выход 1 (продолжить)
* Правило 2: `иначе` -> выход 2 (остановить)
* Проблема: Система обнаруживает прогноз похолодания и начинает заранее греть теплые полы, в то время как в доме никого нет. Это прямая трата энергоресурсов.
* Решение: Аналогично поливу, поток упреждающего прогрева должен быть заблокирован, если `global.get('Home.Mode')` равен "Нет дома" (`Away`). В этом режиме отопление должно работать в экономном режиме поддержки минимальной температуры (+16°C), и упреждающий прогрев до комфортных +24°C не имеет смысла.
Такая простая проверка в начале каждого сложного сценария гарантирует, что автоматика будет работать адекватно и предсказуемо, экономя ресурсы, когда это необходимо.
---
Отказоустойчивость и итоговые рекомендации
Сценарии, зависящие от внешних облачных сервисов, вносят в систему новую точку отказа. Что произойдет, если API погоды станет недоступен из-за проблем с интернетом на объекте или сбоя на стороне самого сервиса? Система не должна "зависать" или переходить в непредсказуемое состояние.
Реализация логики отката (Fallback Logic)
Отказоустойчивость — это способность системы продолжать выполнять свои базовые функции даже при отказе одного из компонентов.Механизмы отслеживания ошибок
* Подключите к этому выходу последовательность узлов, которая отправит уведомление администратору (через Telegram, MQTT и т.д.) и запишет ошибку в системный журнал.
* Дополнительно, на каждой вкладке размещайте узел `catch`, настроенный на `all nodes`, чтобы ловить любые необработанные ошибки, в том числе от API.
Валидация данных (Data Validity)
Даже если API ответил успешно, нет гарантии, что данные корректны. Теоретически, из-за сбоя сервис может вернуть температуру `-300°C` или вероятность осадков `99`.
> ℹ️ Информация: Перед использованием любых данных, полученных из внешнего, неконтролируемого источника, производите проверку на правдоподобность (sanity check).
Например, в узле-обработчике ответа API добавьте простые проверки:
let temp = msg.payload.current.temp;
if (temp < -60 || temp > 60) {
node.error(`Аномальное значение температуры от API: ${temp}°C`);
return null; // Не сохранять и не использовать эти данные
}
Итог
В этом уроке мы рассмотрели, как перейти от реактивного к проактивному управлению климатом, используя данные из внешних API прогноза погоды. Мы реализовали два практических сценария: упреждающий прогрев для систем с высокой инерцией и умный полив, экономящий воду. Ключевым аспектом при внедрении таких сценариев является обеспечение их отказоустойчивости и правильная интеграция с глобальными режимами работы умного дома.
Что дальше?
В следующем уроке мы перейдем к более сложным интеграциям и рассмотрим сценарий `SCN-CLIMATE-015: Адаптивное управление кондиционированием`, в котором система будет учитывать не только температуру, но и положение солнца, время дня и наличие людей в помещении для создания идеального микроклимата с минимальными затратами энергии.