Практика: Мониторинг температуры в нескольких зонах с помощью DS18B20
Введение и постановка задачи
> ℹ️ Информация: Этот урок объединяет навыки, полученные ранее. Убедитесь, что вы изучили материалы по физическому подключению датчиков (урок COURSE-04-M06-L03: Подключение датчиков температуры DS18B20) и базовому опросу в Node-RED (урок COURSE-04-M06-L05: Опрос датчиков в Node-RED).
Цель данного практического занятия — создать полноценную, готовую к эксплуатации систему мониторинга температуры для нескольких критически важных точек на объекте. Эта задача является одной из самых распространенных в автоматизации зданий и малых промышленных объектов. В качестве примера мы рассмотрим систему отопления коттеджа, где необходимо контролировать три ключевых параметра:
Хотя пример сфокусирован на системе отопления, представленная архитектура является универсальным шаблоном, который легко адаптируется для мониторинга любых других объектов: температуры в серверной стойке, в холодильных камерах, в помещениях гостиницы и т.д.
Архитектура нашего решения будет состоять из следующих логических блоков, реализуемых в среде Node-RED на контроллере HI:
- Инициация опроса: Периодический запуск процесса сбора данных с заданной частотой.
- Сбор данных: Одновременный опрос всех подключенных датчиков DS18B20 по их уникальным адресам на шине 1-Wire.
- Агрегация данных: Сбор разрозненных показаний от каждого датчика в единый структурированный объект.
- Нормализация и обогащение: Преобразование технических данных в человеко-читаемый формат, добавление метаданных (например, временной метки) и проверка на корректность.
- Публикация: Отправка готового пакета данных в систему по протоколу MQTT для дальнейшего использования другими подсистемами (логикой управления, дашбордами, системами оповещения).
Этот урок является кульминацией модуля, объединяя ранее изученные теоретические концепции и практические навыки в единое, работающее и масштабируемое решение.
---
Проектирование потока: от опроса до публикации
> 💡 Подсказка: Прежде чем собирать поток в Node-RED, нарисуйте его схему на бумаге или в любом диаграммном редакторе. Это помогает структурировать мысли, заранее выявить потенциальные проблемы в логике и делает процесс разработки более предсказуемым.
Правильное проектирование — залог создания читаемого, поддерживаемого и надежного потока автоматизации. Вместо хаотичного соединения узлов мы будем придерживаться четкой и линейной логической структуры.
Визуализация логики потока
Наш поток будет состоять из пяти основных этапов, каждому из которых соответствует определенный узел или группа узлов в Node-RED:
Визуально эта последовательность выглядит так:
+------------+ +-------------------+
| w1-in |-->| |
| (Sensor 1) | | |
+--------+ +------------+ | | +----------+ +----------+
| Inject |------->+------------+-->| Join |-->| Function |-->| mqtt out |
| (30s) | | w1-in | | (aggregate data) | | (format) | | (publish)|
+--------+ | (Sensor 2) | | | +----------+ +----------+
+------------+ | |
| w1-in | | |
| (Sensor 3) |-->| |
+------------+ +-------------------+
Проектирование структуры данных
Ключевой аспект проектирования — заранее определить, в каком виде мы хотим получить данные на выходе. Неструктурированные данные (`23.5`) или данные с техническими идентификаторами (`{"28-01201e8a0e3a": 65.1}`) усложняют дальнейшую разработку и поддержку.
Наша цель — получить на выходе из узла `Function` строго структурированный JSON-объект.
Пример целевой структуры `msg.payload`:{
"values": {
"supply_pipe": 65.1,
"return_pipe": 58.7,
"boiler_room": 24.3
},
"meta": {
"status": "OK",
"timestamp": 1678886400000,
"source": "heating-controller-flow"
}
}
Такая структура обладает несколькими преимуществами:
- Человеко-читаемые алиасы: Вместо непонятного ID `28-01201e8a0e3a` мы используем логическое имя `supply_pipe`. Это делает код и логику автоматизации самодокументируемыми.
- Разделение данных и метаданных: Сами значения (`values`) отделены от служебной информации (`meta`). Это упрощает их обработку на стороне подписчиков.
- Расширяемость: В будущем мы легко сможем добавить новые поля в `values` или `meta`, не нарушая работу существующих систем, которые уже подписаны на эти данные.
Определив конечную цель, мы можем декомпозировать задачу и настроить каждый узел для ее достижения.
---
Практика: Агрегация данных с датчиков DS18B20
Перейдем к сборке потока в редакторе Node-RED. На этом этапе мы настроим узлы `Inject`, `w1-in` и `Join` для сбора данных со всех датчиков в один объект.
1. Настройка триггера `Inject`
2. Конфигурация узлов `w1-in` для каждого датчика
Теперь необходимо создать узел для каждого из трех наших датчиков. Предположим, система обнаружила следующие ID на шине 1-Wire:
- `28-01201e8a0e3a` (датчик на подающей трубе)
- `28-01201e95817a` (датчик на обратной трубе)
- `28-01201f14b325` (датчик в помещении котельной)
Создайте три узла `w1-in`:
Соедините выход узла `Inject` со входами всех трех узлов `w1-in`. Теперь каждые 30 секунд они будут одновременно пытаться считать температуру со своих датчиков. После успешного чтения каждый узел сгенерирует сообщение, где `msg.payload` будет содержать температуру, а `msg.topic` — заданный нами алиас.
3. Настройка узла `Join` для объединения сообщений
Узел `Join` — это наш агрегатор. Его задача — собрать три отдельных сообщения в одно.
* Mode: Установите `manual`.
* Combine to create: Выберите `a key/value object`. Это скажет узлу `Join` создавать объект, используя `msg.topic` в качестве ключа и `msg.payload` в качестве значения.
* Number of message parts: Установите `3`. Узел будет ждать ровно 3 сообщения, прежде чем сформировать и отправить итоговый объект.
* And every: Установите таймаут, например, `5` секунд. Если за 5 секунд узел не получит все 3 сообщения (например, один из датчиков "повис" или оборвалась линия), он отправит дальше то, что успел собрать. Это защищает систему от полной остановки из-за сбоя одного элемента.
* `Topic` of joined message: Оставьте пустым.
После этих настроек, если все датчики работают штатно, узел `Join` будет генерировать следующее сообщение `msg`:
{
"payload": {
"supply_pipe": 65.1,
"return_pipe": 58.7,
"boiler_room": 24.3
},
"topic": "",
"_msgid": "..."
}
Мы получили агрегированные данные. Теперь их нужно привести к нашему целевому формату.
---
Практика: Нормализация и форматирование данных
> ⚠️ Внимание: Всегда реализуйте проверку на наличие и корректность данных. Если необработанное значение `null` или некорректное число (например, `85` градусов для DS18B20 — это признак ошибки инициализации) попадет в вашу логику автоматизации, это может привести к непредсказуемым последствиям, от ложных тревог до полной остановки работы системы.
На этом этапе мы используем узел `Function` для финальной обработки данных, полученных от `Join`. Эта "швейцарский нож" Node-RED, позволяющий реализовать практически любую логику на JavaScript.
// Получаем объект с температурами из входящего сообщения
const temps = msg.payload;
// Список ожидаемых датчиков (алиасов). Это поможет нам определить, все ли на месте.
const expectedSensors = ["supply_pipe", "return_pipe", "boiler_room"];
let finalValues = {};
let failedSensors = [];
let status = "OK";
// 1. Итерация по ожидаемым датчикам и валидация значений
expectedSensors.forEach(sensorAlias => {
// Проверяем, пришло ли значение от датчика и является ли оно числом
if (temps.hasOwnProperty(sensorAlias) && typeof temps[sensorAlias] === 'number') {
let tempValue = temps[sensorAlias];
// Дополнительная валидация: DS18B20 работает в диапазоне -55 to +125.
// Значение 85 - частый признак ошибки при включении.
if (tempValue > -55 && tempValue < 125 && tempValue !== 85.0) {
finalValues[sensorAlias] = tempValue;
} else {
// Значение вышло за допустимые пределы
finalValues[sensorAlias] = null; // Помечаем как невалидное
failedSensors.push(sensorAlias);
node.warn(`Некорректное значение от датчика ${sensorAlias}: ${tempValue}`);
}
} else {
// Датчик не ответил (не пришел в объекте от узла Join)
finalValues[sensorAlias] = null; // Помечаем как отсутствующее
failedSensors.push(sensorAlias);
node.warn(`Нет данных от датчика: ${sensorAlias}`);
}
});
// 2. Определение общего статуса
if (failedSensors.length > 0) {
if (failedSensors.length === expectedSensors.length) {
status = "TOTAL_FAILURE"; // Все датчики отказали
} else {
status = "PARTIAL_FAILURE"; // Часть датчиков отказала
}
}
// 3. Формирование финального msg.payload по нашему целевому контракту
msg.payload = {
values: finalValues,
meta: {
status: status,
failed_count: failedSensors.length,
failed_list: failedSensors, // Список отказавших для быстрой диагностики
timestamp: Date.now(), // Добавляем точную временную метку
source: "flow-heating-monitor-01"
}
};
// 4. Обновление статуса узла для визуальной диагностики в редакторе
if (status === "OK") {
node.status({ fill: "green", shape: "dot", text: `OK: Подача ${finalValues.supply_pipe}°C` });
} else {
node.status({ fill: "red", shape: "ring", text: `Ошибка: ${status}, отказало ${failedSensors.length}` });
}
// Возвращаем измененное сообщение для передачи следующему узлу
return msg;
Разбор кода:
- Сначала мы получаем объект `temps` и объявляем массив `expectedSensors`. Это делает код более надежным, т.к. мы явно проверяем наличие каждого нужного нам датчика.
- Далее мы итерируемся по списку ожидаемых датчиков. Для каждого проверяем: пришло ли значение (`hasOwnProperty`) и является ли оно числом (`typeof`). Это защищает от ошибок, если датчик вернет, например, строку.
- Проводится дополнительная валидация на соответствие рабочего диапазона DS18B20 и отсекается значение `85`, которое является кодом ошибки по-умолчанию.
- Если датчик не ответил или его значение некорректно, мы заносим его алиас в массив `failedSensors` и выводим предупреждение в лог Node-RED с помощью `node.warn()`.
- На основе количества отказавших датчиков мы формируем общий `status`.
- В конце мы собираем финальный объект `msg.payload`, строго соответствующий нашему проекту, и добавляем в него полезные метаданные: статус, список сбоев и временную метку `Date.now()`.
- Команда `node.status()` позволяет выводить полезную информацию прямо под узлом в редакторе, что упрощает отладку без необходимости постоянно заглядывать в `Debug`.
Теперь наш поток не просто собирает данные, а анализирует их, проверяет на корректность и подготавливает к использованию в виде стандартизированного и обогащенного сообщения.
---
Интеграция с MQTT: Публикация данных для системы
> 🔗 Связанный материал: Подробно о принципах работы с MQTT и настройке брокера на контроллере HI рассказано в модуле по протоколам и интеграциям (COURSE-02-M03: Протокол MQTT для распределенных систем).
Последний шаг — сделать наши данные доступными для всей системы. MQTT — это идеальный инструмент для этой задачи, выступающий в роли центральной "шины данных" для умного объекта.
* Server: Выберите из выпадающего списка ваш MQTT-брокер. На контроллерах HI он обычно предустановлен и доступен по адресу `localhost:1883`.
* Topic: Укажите topic, соответствующий принятой в вашем проекте иерархии. Хорошей практикой является структура `platform/site/system/measurement`. Для нашего примера это будет:
`hi/cottage-1/heating/temperatures`
Такая структура позволяет гибко подписываться на данные. Например, подписка на `hi/cottage-1/heating/#` даст все данные по системе отопления, а `hi/cottage-1/#` — вообще все данные по объекту "Коттедж 1".
* QoS: Установите `1` (At least once). Это обеспечивает гарантированную доставку сообщения, но допускает дубликаты (что для данных мониторинга не критично).
* Retain: Установите `true` (галочка `Retain`). Это означает, что MQTT-брокер сохранит последнее сообщение в этом топике. Любой новый клиент (например, панель визуализации), подписавшись на этот топик, немедленно получит последнее актуальное состояние температур, не дожидаясь следующего цикла опроса.
* Name: "Публикация в MQTT".
После развертывания потока (`Deploy`) каждые 30 секунд в топик `hi/cottage-1/heating/temperatures` будет публиковаться наш JSON-объект.
Для проверки корректности работы можно использовать любой внешний MQTT-клиент, например, программу MQTT Explorer. Подключившись к брокеру, вы должны увидеть ваш топик и поступающие в него сообщения в реальном времени, что подтверждает успешное завершение нашей работы.
---
Заключение: Итоги и дальнейшие шаги
В рамках этого урока мы прошли полный цикл создания практического решения: от постановки задачи и проектирования до реализации, отладки и интеграции. Мы создали надежный и эффективный поток Node-RED, который решает одну из самых фундаментальных задач автоматизации — мониторинг температуры в нескольких зонах.
Ключевые результаты:- Реализован параллельный опрос нескольких датчиков DS18B20.
- Данные агрегируются в единый объект с помощью узла `Join` в режиме `key/value`.
- Внедрен узел `Function` для нормализации, валидации и обогащения данных, что делает систему устойчивой к сбоям отдельных датчиков.
- Финальные, структурированные данные публикуются в MQTT, становясь доступными для всей экосистемы умного объекта.
- Преимущества: Созданный поток нагляден, его логика легко читается. Он эффективно использует ресурсы контроллера и устойчив к частичным отказам.
- Зоны для улучшения (для продвинутых уровней):
* Продвинутая обработка ошибок: Можно добавить логику повторных попыток чтения для "зависшего" датчика или отправку push-уведомлений администратору при полном отказе системы мониторинга.
Что дальше?
Собранные и опубликованные в MQTT данные являются не конечной целью, а ценным ресурсом для создания более сложной автоматизации:
- Логика управления: Можно создать новый поток, который подписывается на топик `hi/cottage-1/heating/temperatures` (`mqtt in`), анализирует разницу температур подачи и обратки и на основе этого корректирует работу циркуляционного насоса.
- Система оповещений (алертинг): Другой поток может проверять `boiler_room.temperature` и, если она превысит заданный порог (например, 40°C), отправлять тревожное сообщение администратору через Telegram или email.
- Визуализация: Данные из MQTT могут быть легко интегрированы с системами визуализации, такими как Grafana, для построения исторических графиков, или с панелью Node-RED Dashboard для отображения текущих значений.
Вы успешно освоили базовый, но чрезвычайно важный паттерн сбора данных. Теперь вы готовы применять его для решения широкого круга задач и переходить к созданию на его основе сложной логики управления.