Практика: Получение прогноза погоды и включение полива
Введение: Архитектура решения для умного полива
В современной системе автоматизации, особенно на объектах типа "умный дом" или "коттедж", критически важна способность системы взаимодействовать с внешним миром. Простые сценарии, основанные только на внутренних датчиках (например, "включить свет по датчику движения"), являются базовым уровнем. Настоящая интеллектуальная автоматизация начинается там, где система принимает решения, основываясь на данных из внешних источников, таких как интернет-сервисы.
Постановка задачи: В этом уроке мы решим практическую и востребованную задачу — создадим автоматическую систему полива газона, которая будет работать не просто по таймеру, а с учетом реального прогноза погоды. Цель — экономить воду и обеспечивать оптимальный уход за растениями, отменяя полив, если недавно был или скоро ожидается дождь, или если температура слишком низкая для испарения. Обзор архитектуры:Для решения этой задачи мы построим поток в Node-RED, который объединит два ключевых сетевых протокола: HTTP для получения данных извне и MQTT для управления устройствами внутри локальной сети.
Такая архитектура является примером мощного и гибкого подхода. Она разделяет логику сбора данных и логику исполнения, что делает систему более надежной и масштабируемой.
📋 Ключевые понятия:
- Интеграция сторонних API: Процесс подключения нашей системы к внешним программам и сервисам для обмена данными.
- Комбинирование протоколов: Использование разных протоколов (HTTP для запроса данных из интернета, MQTT для локальных команд) для решения одной задачи, используя сильные стороны каждого.
- Автоматизация на основе внешних данных: Принятие решений системой автоматизации не только на основе показаний локальных датчиков, но и на базе информации, полученной из глобальных сетей.
Наш финальный поток будет представлять собой элегантную и логичную цепочку узлов:
[Inject] -> [HTTP Request] -> [JSON] -> [Function] -> [MQTT Out]
- `Inject`: Инициирует запуск потока (например, каждое утро в 6:00).
- `HTTP Request`: Отправляет запрос к погодному API.
- `JSON`: Гарантирует, что текстовый ответ от сервера будет преобразован в JavaScript-объект (хотя узел HTTP Request может делать это сам).
- `Function`: "Мозг" нашей системы, где анализируется погода.
- `MQTT Out`: Отправляет команду "Включить полив" на исполнительное устройство.
---
Шаг 1: Получение данных о погоде через HTTP API
Первым и самым важным шагом является получение надежных данных о погоде. Для этого мы будем использовать Application Programming Interface (API) — программный интерфейс, который позволяет нашей системе "общаться" с другим сервисом. В качестве примера мы воспользуемся популярным и доступным сервисом OpenWeatherMap.
Регистрация и получение API-ключа
> ⚠️ Внимание: Храните ваш API-ключ в безопасности. Не публикуйте его в открытом доступе. Если ключ будет скомпрометирован, его можно будет перегенерировать в личном кабинете.
Настройка узла HTTP Request
Теперь, когда у нас есть ключ, мы можем настроить узел `HTTP Request` в Node-RED для получения данных.
`https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API-ключ}&units=metric`
Замените плейсхолдеры на ваши реальные данные:
* `{lat}`: Широта вашего объекта (например, `55.7558`).
* `{lon}`: Долгота вашего объекта (например, `37.6173`).
* `{API-ключ}`: Ваш ключ, полученный на сайте.
Пример готового URL:
`https://api.openweathermap.org/data/2.5/weather?lat=55.7558&lon=37.6173&appid=ВАШ_КЛЮЧ_ЗДЕСЬ&units=metric`
> 💡 Подсказка: Параметр `&units=metric` указывает API вернуть температуру в градусах Цельсия. Без него по умолчанию будут градусы Кельвина.
Анализ ответного объекта
Теперь проверим, что все работает. Соедините узел `Inject` с входом узла `HTTP Request`, а его выход — с узлом `Debug`. Разверните поток (Deploy) и нажмите на кнопке узла `Inject`.
В панели отладки (Debug) вы должны увидеть большой объект. Это и есть наш JSON-ответ от сервера погоды. Давайте проанализируем его структуру, она будет примерно такой:
{
"coord": {
"lon": 37.6173,
"lat": 55.7558
},
"weather": [
{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01d"
}
],
"base": "stations",
"main": {
"temp": 19.5,
"feels_like": 19.3,
"temp_min": 18.0,
"temp_max": 21.0,
"pressure": 1012,
"humidity": 60
},
"visibility": 10000,
"wind": {
"speed": 2.5,
"deg": 180
},
"clouds": {
"all": 0
},
"dt": 1661860901,
"sys": {
"type": 2,
"id": 2022833,
"country": "RU",
"sunrise": 1661826581,
"sunset": 1661877636
},
"timezone": 10800,
"id": 524901,
"name": "Moscow",
"cod": 200
}
Ключевые для нас поля:
- `msg.payload.main.temp`: Текущая температура.
- `msg.payload.weather[0].main`: Основное описание погоды (например, "Clear", "Clouds", "Rain"). Обратите внимание, что `weather` — это массив, поэтому мы обращаемся к его первому элементу `[0]`.
На этом шаге мы успешно настроили получение данных из внешнего мира. Наш контроллер теперь "знает", какая погода за окном.
---
Шаг 2: Обработка данных и логика принятия решений
Получить данные — это только половина дела. Теперь нам нужно их проанализировать и на их основе принять осмысленное решение. Эту задачу выполнит узел `Function`, который позволяет писать произвольный код на JavaScript.
Добавьте узел `Function` после узла `HTTP Request`. В нем мы реализуем наш основной алгоритм.
Разработка алгоритма
Определим наши бизнес-требования к системе полива:
Написание кода в узле Function
Откройте узел `Function` и вставьте следующий код:
// --- Параметры сценария ---
const MIN_TEMP_FOR_WATERING = 10; // Минимальная температура для полива, °C
const WATERING_DURATION_MIN = 15; // Длительность полива, минут
// --- Получение данных из входящего сообщения ---
// В msg.payload находится объект, полученный от погодного API
const weatherData = msg.payload;
// 1. --- Валидация входящих данных ---
// Перед работой с данными всегда проверяем, что они существуют и имеют нужную структуру.
// Это защищает поток от падения в случае ошибки API или изменения формата ответа.
if (!weatherData || !weatherData.main || !weatherData.weather || !Array.isArray(weatherData.weather) || weatherData.weather.length === 0) {
node.error("Ошибка: получен некорректный или неполный объект погоды.", msg);
// Останавливаем выполнение потока, не передавая сообщение дальше
return null;
}
const currentTemp = weatherData.main.temp;
const weatherCondition = weatherData.weather[0].main;
// Вывод отладочной информации в панель Debug
node.warn(`Проверка погоды: Температура=${currentTemp}°C, Условия='${weatherCondition}'`);
// 2. --- Логика принятия решения ---
let isWateringNeeded = true;
let reason = "";
if (weatherCondition.toLowerCase().includes('rain')) {
isWateringNeeded = false;
reason = "Обнаружен дождь в прогнозе.";
} else if (currentTemp < MIN_TEMP_FOR_WATERING) {
isWateringNeeded = false;
reason = `Температура (${currentTemp}°C) ниже порогового значения (${MIN_TEMP_FOR_WATERING}°C).`;
}
// 3. --- Формирование исходящего сообщения ---
if (isWateringNeeded) {
// Условия для полива подходящие.
// Формируем новое сообщение для отправки в MQTT.
// Старый msg.payload (с погодой) нам больше не нужен.
// Создаем новый payload по нашему контракту сообщения для управления поливом.
msg.payload = {
"command": "START",
"duration_minutes": WATERING_DURATION_MIN
};
// Добавляем информацию для логирования
msg.audit_log = `Полив запущен. Условия: t=${currentTemp}°C, ${weatherCondition}.`;
// Обновляем статус узла для визуальной диагностики
node.status({ fill: "green", shape: "dot", text: `Полив РАЗРЕШЕН. t=${currentTemp}°C` });
// Отправляем сформированное сообщение дальше по потоку
return msg;
} else {
// Полив не требуется.
// Обновляем статус узла и останавливаем поток.
node.status({ fill: "yellow", shape: "ring", text: `Полив ОТМЕНЕН. Причина: ${reason}` });
// Возврат null прерывает цепочку, и сообщение не пойдет на следующий узел (MQTT Out).
return null;
}
> 💡 Подсказка: Для отладки кода в узле `Function` используйте `node.warn()` или `node.error()`. Эти вызовы выводят отладочную информацию в боковую панель Debug, но в отличие от `node.log()`, их можно включать и отключать в настройках узла, что удобно на работающей системе.
Разбор кода:- Константы: Мы выносим ключевые параметры (`MIN_TEMP_FOR_WATERING`, `WATERING_DURATION_MIN`) в начало кода. Это делает сценарий легко настраиваемым.
- Валидация: Первым делом мы проверяем, что объект `msg.payload` и его вложенные свойства (`main`, `weather`) существуют. Это эталонный пример защитного программирования (defensive programming), который предотвращает крах потока.
- Логика: Простой `if-else if` блок проверяет наши условия. Обратите внимание на `toLowerCase().includes('rain')`, что делает проверку нечувствительной к регистру ("Rain", "rain").
- Формирование `msg`: Если полив нужен, мы ПОЛНОСТЬЮ заменяем `msg.payload` на новый объект, который понятен нашему исполнительному устройству. Если полив не нужен, мы возвращаем `null`, что является стандартным паттерном в Node-RED для прерывания потока.
- Визуальный статус: Использование `node.status()` позволяет прямо в редакторе видеть текущее решение узла, что невероятно упрощает диагностику.
Теперь, после узла `Function`, у нас либо ничего не выходит (если полив не нужен), либо выходит сообщение с четкой командой.
---
Шаг 3: Отправка команды на включение полива по MQTT
Финальный этап — передача нашей команды исполнительному механизму. Как мы уже знаем из предыдущих уроков, протокол MQTT идеально подходит для этой задачи благодаря своей легковесности и надежности в рамках локальной сети.
Настройка узла MQTT Out
Добавьте на поле узел `MQTT Out` и соедините его с выходом узла `Function`.
`HI/outdoor/garden/watering/set`
* `HI`: Корень нашего проекта.
* `outdoor`: Расположение (улица).
* `garden`: Конкретная зона (сад).
* `watering`: Тип устройства (полив).
* `set`: Намерение (установить состояние).
> ⚠️ Внимание: Всегда проверяйте, что топик, в который вы отправляете команду (`MQTT Out`), не совпадает с топиком, на который вы подписаны для получения статуса (`MQTT In`). Например, `.../set` для команд и `.../state` для статуса. Это предотвращает создание бесконечных циклов сообщений, когда система начинает реагировать сама на себя.
Формат полезной нагрузки (Payload)
Наш узел `Function` уже подготовил `msg.payload` в удобном JSON-формате:
{
"command": "START",
"duration_minutes": 15
}
Этот формат гораздо предпочтительнее, чем простая строка `"ON"`, по нескольким причинам:
- Гибкость: Мы можем легко добавить новые параметры (например, `"zone": "front_lawn"`) без изменения всей логики.
- Информативность: Сообщение само по себе описывает не только действие (`START`), но и его параметры (`duration_minutes`).
- Стандартизация: Использование JSON для всех команд в проекте упрощает разработку и отладку.
Исполнительное устройство (другой контроллер или ESP-модуль), подписанное на топик `HI/outdoor/garden/watering/set`, получит этот JSON, распарсит его и включит реле на указанные 15 минут.
После настройки всех узлов и развертывания потока (Deploy), ваша система готова. При каждом срабатывании узла `Inject`, она будет запрашивать погоду, анализировать ее и, при необходимости, отправлять команду на полив.
---
Резюме и дальнейшие шаги
В этом уроке мы создали полноценный и полезный сценарий автоматизации, который демонстрирует мощь платформы HI и Node-RED в интеграции различных систем и протоколов. Мы успешно объединили данные из внешнего мира, полученные по HTTP API, с внутренней системой управления, работающей на MQTT.
Закрепленные навыки:- Настройка узла `HTTP Request` для работы со сторонними RESTful API, включая передачу API-ключа.
- Парсинг JSON-ответов и извлечение из них нужных данных с помощью точечной нотации (например, `msg.payload.main.temp`).
- Реализация сложной условной логики в узле `Function` с валидацией данных, принятием решений и формированием нового управляющего сообщения.
- Корректная настройка узла `MQTT Out` для публикации команд, включая выбор правильного QoS и флага Retain.
Возможные улучшения и развитие сценария
Созданный нами поток — это отличная основа, которую можно и нужно развивать. Вот несколько идей для дальнейшего совершенствования:
Что дальше
В следующем модуле мы углубимся в вопросы хранения данных. Мы научимся сохранять историю показаний датчиков, логи событий и состояния системы в базу данных MySQL, которая является неотъемлемой частью контроллера HI, что позволит нам строить графики, анализировать тренды и создавать еще более сложные и надежные системы автоматизации.