Сбор, обработка и передача данных с Modbus-сенсора влажности
COURSE-16-M03-LAB01 — Сбор, обработка и передача данных с Modbus-сенсора влажности
1. Цели лабораторной работы
По завершении данной лабораторной работы инженер сможет:
- Настроить и запустить опрос Modbus-устройства (датчика влажности) на контроллере HI.
- Применить фундаментальные паттерны Node-RED: "Контракт сообщения", "Обработка ошибок" и "Визуальный статус".
- Выполнить валидацию и преобразование сырых данных с датчика в стандартизированный JSON-формат.
- Опубликовать обработанные данные в топик MQTT для дальнейшего использования другими системами.
- Провести базовую диагностику и устранение неисправностей в цепочке "Датчик -> Контроллер -> MQTT".
2. Оборудование и программное обеспечение
- Контроллер: Контроллер HI (любая модель), подключенный к сети.
- Программное обеспечение: Среда исполнения Node-RED с установленной палитрой `node-red-contrib-modbus`.
- Сенсор: Датчик температуры и влажности с интерфейсом Modbus RTU (например, WB-MSW3 или аналог), подключенный к порту RS-485 контроллера.
- MQTT-брокер: Доступный в сети MQTT-брокер (может быть развернут на самом контроллере HI).
3. Предварительная подготовка
📋 Чек-лист готовности к работе:
* Клемма `A` датчика -> Клемма `A` порта RS-485 контроллера.
* Клемма `B` датчика -> Клемма `B` порта RS-485 контроллера.
* Подано питание на датчик (например, 24V DC).
💡 Карта регистров используемого датчика (пример):
Для данной лабораторной работы мы будем использовать следующие допущения о карте регистров нашего датчика:
- Регистр относительной влажности: `105` (тип: Input Register).
- Формат данных: Целое число. Значение `567` соответствует `56.7% RH`.
- Slave ID: `25`.
4. Сценарий выполнения
Мы создадим поток Node-RED, который каждые 15 секунд опрашивает датчик влажности, проверяет корректность данных, приводит их к стандартному формату и отправляет в MQTT.
Архитектура потока (ASCII Flow Diagram): +---------------------------+ +----------------------+
[Inject: 15s] -> [Modbus-Getter] -> | Function: Validate/Format | -> | mqtt out |
(Опрос) (Чтение RH) | (Основная логика) | | (Публикация данных) |
+---------------------------+ +----------------------+
| (on error)
v
+------------------+
[Catch: All Nodes]-----------------> | Function: LogErr | -> [Debug: Ошибки]
(Перехват ошибок) +------------------+
Шаг 1: Создание и настройка узла опроса
* `Payload`: `timestamp`
* `Repeat`: `interval`
* `every`: `15` `seconds`
* `Name`: `Опрос каждые 15 сек`
* Нажмите Done.
Шаг 2: Настройка узла чтения Modbus
* `Name`: `Читать влажность (RH)`
* `Server`: Нажмите на иконку карандаша для создания нового Modbus-клиента.
* `Type`: `RTU-BUFFERED`
* `Serial Port`: Укажите порт вашего контроллера, например, `/dev/ttyRS485-1`.
* `Baud Rate`: `9600`
* `Data Bits`: `8`
* `Parity`: `None`
* `Stop Bits`: `1`
* Нажмите Add, а затем выберите созданный сервер.
* `Unit-ID`: `25` (Slave ID вашего датчика).
* `FC`: `FC 4: Read Input Registers`.
* `Address`: `105` (адрес регистра влажности).
* `Quantity`: `1`.
* Нажмите Done.
Шаг 3: Разработка узла логики (валидация и форматирование)
Это самый важный узел, реализующий паттерны "Контракт сообщения" и "Визуальный статус".
* `Name`: `Validate & Format RH`
* `Outputs`: `1`
* Вставьте следующий код в редактор:
// ID этого сценария для логирования и отладки
const SENSOR_SOURCE_ID = "humidity-sensor-room101";
// 1. Получение сырого значения из ответа Modbus
// Узел Modbus-Getter возвращает массив в msg.payload
let rawValue = msg.payload[0];
// 2. Валидация данных
// Проверяем, что значение находится в допустимом диапазоне (0-1000, т.е. 0.0-100.0%)
if (rawValue === undefined || rawValue < 0 || rawValue > 1000) {
// Паттерн "Визуальный статус": отображаем ошибку на узле
node.status({ fill: "red", shape: "dot", text: "Invalid data: " + rawValue });
// Паттерн "Обработка ошибок": генерируем ошибку, которая будет поймана узлом Catch
node.error("Некорректное значение влажности: " + rawValue, msg);
return null; // Прерываем выполнение потока для этого сообщения
}
// 3. Преобразование данных
// Делим на 10, чтобы получить реальное значение влажности
let humidity = rawValue / 10.0;
// 4. Паттерн "Контракт сообщения": формируем стандартный объект msg.payload
msg.payload = {
value: humidity,
unit: "%RH",
source: SENSOR_SOURCE_ID,
ts: Date.now()
};
// 5. Установка топика для MQTT
msg.topic = "hi/telemetry/room101/humidity";
// 6. Паттерн "Визуальный статус": отображаем успешный результат
node.status({ fill: "green", shape: "dot", text: "OK: " + humidity + " %RH" });
return msg;
Шаг 4: Настройка публикации в MQTT
* `Server`: Выберите или настройте ваш MQTT-брокер.
* `Topic`: Оставьте пустым, так как топик устанавливается в `msg.topic`.
* `QoS`: `1`
* `Retain`: `false`
* `Name`: `Публикация в MQTT`
* Нажмите Done.
Шаг 5: Настройка централизованной обработки ошибок
* `Scope`: `all nodes on current flow` (ловить ошибки со всех узлов на этой вкладке).
* `Name`: `Перехват ошибок`
* Нажмите Done.
⚠️ Важно: В реальном проекте вместо узла `debug` здесь будет субпоток-логгер (`FLOW-SYSTEM-LOG-001`), который записывает ошибку в MySQL, отправляет уведомление администратору и т.д. Для данной лабораторной работы достаточно вывода в панель отладки.
Шаг 6: Развертывание и проверка
5. Скелет потока (Flow Skeleton)
Вы можете импортировать этот JSON-код через меню Node-RED (`Import`), чтобы получить готовую структуру потока.
[
{
"id": "a1b2c3d4.e5f6g7",
"type": "tab",
"label": "COURSE-16-M03-LAB01",
"disabled": false,
"info": ""
},
{
"id": "1a2b3c4d.5e6f7g",
"type": "inject",
"z": "a1b2c3d4.e5f6g7",
"name": "Опрос каждые 15 сек",
"props": [
{
"p": "payload"
}
],
"repeat": "15",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 150,
"y": 100,
"wires": [
[
"8h9i0j1k.2l3m4n"
]
]
},
{
"id": "8h9i0j1k.2l3m4n",
"type": "modbus-getter",
"z": "a1b2c3d4.e5f6g7",
"name": "Читать влажность (RH)",
"showStatusActivities": true,
"showErrors": true,
"logIOActivities": false,
"unitid": "25",
"dataType": "InputRegister",
"adr": "105",
"quantity": "1",
"server": "YOUR_MODBUS_SERVER_ID",
"x": 380,
"y": 100,
"wires": [
[
"5p6q7r8s.9t0u1v"
],
[]
]
},
{
"id": "5p6q7r8s.9t0u1v",
"type": "function",
"z": "a1b2c3d4.e5f6g7",
"name": "Validate & Format RH",
"func": "const SENSOR_SOURCE_ID = \"humidity-sensor-room101\";\nlet rawValue = msg.payload[0];\nif (rawValue === undefined || rawValue < 0 || rawValue > 1000) {\n node.status({ fill: \"red\", shape: \"dot\", text: \"Invalid data: \" + rawValue });\n node.error(\"Некорректное значение влажности: \" + rawValue, msg);\n return null;\n}\nlet humidity = rawValue / 10.0;\nmsg.payload = {\n value: humidity,\n unit: \"%RH\",\n source: SENSOR_SOURCE_ID,\n ts: Date.now()\n};\nmsg.topic = \"hi/telemetry/room101/humidity\";\nnode.status({ fill: \"green\", shape: \"dot\", text: \"OK: \" + humidity + \" %RH\" });\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 630,
"y": 100,
"wires": [
[
"2w3x4y5z.6a7b8c"
]
]
},
{
"id": "2w3x4y5z.6a7b8c",
"type": "mqtt out",
"z": "a1b2c3d4.e5f6g7",
"name": "Публикация в MQTT",
"topic": "",
"qos": "1",
"retain": "false",
"broker": "YOUR_MQTT_BROKER_ID",
"x": 870,
"y": 100,
"wires": []
},
{
"id": "d9e8f7g6.h5i4j3",
"type": "catch",
"z": "a1b2c3d4.e5f6g7",
"name": "Перехват ошибок",
"scope": null,
"uncaught": false,
"x": 370,
"y": 200,
"wires": [
[
"k2l1m0n9.o8p7q6"
]
]
},
{
"id": "k2l1m0n9.o8p7q6",
"type": "debug",
"z": "a1b2c3d4.e5f6g7",
"name": "Лог ошибок",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 570,
"y": 200,
"wires": []
}
]
6. Чек-лист для самопроверки
- [ ] Поток развернут без ошибок?
- [ ] Узел `inject` запускается каждые 15 секунд?
- [ ] Узел `Modbus-Getter` успешно считывает данные (не показывает ошибку `timeout`)?
- [ ] Узел `function` отображает зеленый статус с корректным значением влажности?
- [ ] В MQTT-клиенте, подписанном на топик `hi/telemetry/room101/humidity`, появляются сообщения?
- [ ] Формат сообщения в MQTT соответствует "Контракту сообщения" (содержит `value`, `unit`, `source`, `ts`)?
- [ ] Если физически отключить датчик от шины RS-485, узел `catch` перехватывает ошибку и выводит ее в панель отладки?
7. Возможные усложнения (Advanced Practice)
- Добавить чтение температуры: Модифицируйте поток, чтобы он считывал также регистр температуры, и отправлял данные в отдельный топик `hi/telemetry/room101/temperature`.
- Создать субпоток: Вынесите логику опроса и форматирования данных с одного Modbus-устройства в переиспользуемый субпоток (`subflow`).
- Сохранение в базу данных: Добавьте в поток узел `mysql` для сохранения каждого измерения в таблицу `telemetry_log` в базе данных MySQL на контроллере.
- Аварийное оповещение: Добавьте узел `switch`, который будет проверять, не превышает ли влажность пороговое значение (например, 75%), и в случае превышения отправлять аварийное сообщение в отдельный MQTT-топик `hi/alarms/high_humidity`.
8. Рубрика оценивания
| Критерий | Макс. баллов | Описание |
| :--- | :--- | :--- |
| Функциональность | 40 | Поток успешно считывает данные с Modbus-устройства и публикует их в MQTT с заданной периодичностью. |
| Применение паттернов | 30 | Корректно реализованы: 1) Валидация и "Контракт сообщения" в узле `function`. 2) Использование `node.status` для визуальной обратной связи. 3) Наличие узла `catch` для перехвата ошибок. |
| Качество кода и конфигурации | 20 | Узлы имеют осмысленные имена. Код в узле `function` читаем и содержит комментарии. Конфигурация узлов (Modbus, MQTT) выполнена корректно. |
| Диагностика | 10 | Инженер может продемонстрировать работу системы обработки ошибок, искусственно создав сбой (например, отключив датчик). |
| Итого | 100 | |
9. Runbook: "Что делать, если не работает"
| Проблема | Возможная причина | Действия по устранению |
| :--- | :--- | :--- |
| Узел `Modbus-Getter` показывает ошибку `timeout` или `Port not open` | 1. Неправильно указан COM-порт.
2. Физический обрыв линии RS-485.
3. Перепутаны провода A/B.
4. Несовпадение параметров шины (скорость, четность).
5. Неправильный Slave ID. | 1. Проверьте имя порта в ОС Linux контроллера (`ls /dev/tty*`).
2. "Прозвоните" кабель. Проверьте надежность зажимов в клеммах.
3. Поменяйте местами провода A и B на клеммах контроллера.
4. Убедитесь, что настройки в узле Node-RED и на датчике идентичны (`9600 8N1`).
5. Проверьте Slave ID, установленный на датчике. |
| Данные приходят, но они некорректны (например, `0` или `65535`) | 1. Неправильный адрес регистра.
2. Неправильный тип регистра (Input vs Holding).
3. Ошибка "off-by-one" (адрес `105` вместо `104`). | 1. Внимательно перечитайте карту регистров устройства.
2. Убедитесь, что вы используете правильную функцию Modbus (FC4 для Input Registers).
3. Попробуйте указать адрес на единицу меньше. |
| Узел `mqtt out` показывает статус `disconnected` | 1. Неправильный адрес или порт MQTT-брокера.
2. Брокер недоступен по сети.
3. Неверные учетные данные (логин/пароль). | 1. Проверьте настройки MQTT-сервера в Node-RED.
2. С контроллера выполните команду `ping <адрес_брокера>`, чтобы проверить сетевую доступность.
3. Проверьте логин и пароль в конфигурации узла. |
| Поток работает, но узел `function` показывает красный статус `Invalid data` | 1. Датчик неисправен и отдает некорректные значения.
2. Логика валидации в коде слишком строгая. | 1. Проверьте сырые данные, приходящие от `Modbus-Getter` в панели отладки.
2. Временно ослабьте условия в `if (rawValue < 0 || rawValue > 1000)` для диагностики. |