Практика: Опрос Modbus-счетчика электроэнергии
Постановка задачи и обзор оборудования
остановка задачи и обзор оборудования
Цель этого урока — разработать в Node-RED полноценное решение для сбора данных с промышленного счетчика электроэнергии. Мы научимся настраивать периодический опрос устройства по шине RS-485, считывать его ключевые показания — напряжение, ток, активную мощность и суммарное потребление — а затем обрабатывать полученные данные и публиковать их в систему мониторинга через протокол MQTT. Этот навык является основой для построения систем учета энергоресурсов, мониторинга нагрузки и предиктивного обслуживания оборудования.
> 💡 Подсказка: Всегда начинайте работу с новым Modbus-устройством с изучения его карты регистров. Это сэкономит часы отладки. Обратите внимание на тип данных (Float, Int32), порядок байт (BE/LE) и смещение адресов (часто адреса в документации начинаются с 1, а в запросе нужно указывать 0).
Обзор типового оборудования
В качестве примера мы будем использовать распространенный однофазный счетчик SDM120-Modbus. Это компактное устройство, монтируемое на DIN-рейку, идеально подходящее для задач субучета в офисах, квартирах или для мониторинга отдельных производственных линий.
Ключевые характеристики:- Интерфейс: RS-485, протокол Modbus RTU.
- Измеряемые параметры: Напряжение, ток, активная/реактивная/полная мощность, частота, коэффициент мощности, суммарная потребленная энергия.
- Настройка: Параметры шины (адрес, скорость, четность) настраиваются непосредственно через меню самого устройства.
Карта регистров Modbus
Техническая документация (datasheet) на устройство — ваш самый главный инструмент. В ней содержится карта регистров (Register Map), которая описывает, по какому адресу находится тот или иной параметр.
Для счетчика SDM120-Modbus большинство данных — это 32-битные числа с плавающей запятой (Floating Point), которые занимают два последовательных 16-битных регистра Modbus. Все они доступны только для чтения (Function Code 04: Read Input Registers).
| Параметр | Адрес регистра (Dec) | Тип данных | Количество регистров | Описание |
| ------------------- | -------------------- | ------------- | -------------------- | ---------------------------------------- |
| Напряжение (Voltage) | 0 | 32-bit Float | 2 | Напряжение в Вольтах (V) |
| Ток (Current) | 6 | 32-bit Float | 2 | Ток в Амперах (A) |
| Активная мощность (P) | 12 | 32-bit Float | 2 | Мощность в Ваттах (W) |
| Частота (Frequency) | 70 | 32-bit Float | 2 | Частота сети в Герцах (Hz) |
| Общая энергия (Total) | 342 | 32-bit Float | 2 | Суммарная потребленная энергия в кВт*ч |
> ⚠️ Внимание: Обратите внимание на адреса. Адрес "0" для напряжения означает, что мы начинаем читать с самого первого регистра. Адрес "6" для тока означает, что мы должны пропустить 6 адресов (три 32-битных значения) от начала.
Архитектура решения и Ops-слой
Наше решение будет состоять из следующих логических блоков, которые мы последовательно реализуем:
Так мы построим надежный и масштабируемый поток телеметрии от полевого устройства до центральной системы с автоматическим контролем состояния связи.
Физическое подключение и настройка шины RS-485
Корректное физическое подключение — залог стабильной работы всей системы. Ошибки на этом этапе являются наиболее частой причиной проблем со связью.
> ⚠️ Внимание: Неправильное подключение линий A/B не выведет из строя оборудование, но связь работать не будет. Самая частая ошибка — перепутанные клеммы. Если данные не читаются, первое, что нужно сделать — поменять A и B местами.
Схема подключения
Для подключения счетчика к контроллеру HI используется кабель "витая пара".
- Линия A счетчика подключается к клемме A (или D+) порта RS-485 на контроллере.
- Линия B счетчика подключается к клемме B (или D-) порта RS-485 на контроллере.
- GND (Земля): Настоятельно рекомендуется соединять клеммы GND счетчика и контроллера. Это выравнивает потенциалы устройств и значительно повышает помехоустойчивость шины, особенно на длинных линиях.
Пример ASCII-схемы подключения (`WIRING-METER-001`):
//======== WIRING-METER-001: Main Energy Meter Connection =========
[CTRL:HI-Core] (METER:SDM120:ID-1)
(RS485-1)
Клемма Цвет Клемма
A ----------- (Зеленый) ------------------- A
B ----------- (Белый) -------------------- B
GND ----------- (Черный) ------------------- GND
Терминирование шины
Шина RS-485 требует установки согласующих резисторов (терминаторов) номиналом 120 Ом на двух крайних устройствах линии для предотвращения отражения сигнала.
- Если контроллер HI и счетчик — единственные устройства на шине, то терминаторы нужно установить с обеих сторон.
- Если на шине несколько устройств, терминаторы ставятся только на первом и последнем в физической цепи.
Настройка параметров на счетчике
Перед подключением к Node-RED необходимо убедиться, что параметры связи на самом счетчике установлены правильно. Для SDM120 это делается через его меню.
Таким образом, наши параметры шины: 9600 8N1. Эти же значения мы будем использовать при настройке клиента в Node-RED.
Идентификация порта в Linux
Контроллер HI работает под управлением Debian. Физические порты RS-485 представлены в системе как файлы устройств в директории `/dev/`. Нам нужно определить, какой файл соответствует нашему порту.
- Если порт RS-485 встроен в контроллер, он, скорее всего, будет иметь имя вида `/dev/ttyS0`, `/dev/ttyS1` и т.д.
- Если вы используете внешний USB-to-RS485 адаптер, его имя будет `/dev/ttyUSB0`, `/dev/ttyUSB1` и т.д.
Самый простой способ найти порт — выполнить команду в терминале контроллера до и после подключения адаптера:
ls /dev/tty*
Новое появившееся устройство и будет вашим портом. Также можно использовать команду `dmesg | grep tty`, чтобы увидеть сообщения ядра о подключении нового последовательного устройства. Запомните этот путь, он понадобится нам в следующей секции.
---
Конфигурация узлов Modbus в Node-RED
Теперь, когда физическое подключение готово и параметры шины известны, переходим к программной настройке в среде Node-RED.
> 🔗 Связанный материал: Мы уже рассматривали основы работы с шиной RS-485 и палитрой `node-red-contrib-modbus` в уроке COURSE-06-M07-L06 "Работа с шиной RS-485: основы Modbus RTU". Рекомендуется повторить материал перед выполнением практической части.
Установка палитры
Если палитра `node-red-contrib-modbus` еще не установлена, сделайте это через `Menu -> Manage palette -> Install`. Введите в поиске `modbus` и установите указанный пакет. После установки в левой панели появятся новые узлы: `Modbus-Read`, `Modbus-Write`, `Modbus-Getter` и другие.
Создание конфигурации Modbus-клиента
* Name: Дайте клиенту осмысленное имя, например `RS485-Bus-1`.
* Type: Выберите `Serial`.
* Serial Port: Укажите путь к вашему порту, который мы определили ранее (например, `/dev/ttyS1` или `/dev/ttyUSB0`).
* Serial Type: Выберите `RTU buffer`.
* Baud Rate: Укажите `9600`.
* Data-Bits: `8`.
* Parity: `None`.
* Stop-Bits: `1`.
* Unit-Id: Это поле можно оставить пустым, так как мы будем указывать адрес для каждого запроса отдельно.
Настройка узла Modbus-Read
Вернемся в окно настроек узла `Modbus-Read`. Теперь нам нужно указать, что именно мы хотим прочитать.
Нажмите `Done`. В результате узел `Modbus-Read` будет каждые 10 секунд отправлять запрос на шину RS-485 устройству с адресом `1`, прося его вернуть 2 регистра, начиная с адреса `0`. Ответ будет выходить из единственного выхода узла в виде объекта `msg`.
---
Парсинг данных с помощью Function Node
Ответ от узла `Modbus-Read` приходит не в виде готового числа, а в виде бинарного пакета данных. Наша задача — "распаковать" его и извлечь осмысленное значение.
Анализ ответа от Modbus-Read
Подключите узел `Debug` к выходу вашего узла `Modbus-Read` и разверните проект. В панели отладки вы увидите сообщение. Его `msg.payload` будет выглядеть примерно так:
{
"data": [17285, 23781],
"buffer": {
"type": "Buffer",
"data": [67, 133, 92, 229]
}
}
- `data`: Это массив из двух 16-битных чисел, которые составляют наше 32-битное значение. Работать с ними напрямую неудобно.
- `buffer`: Это объект Buffer, который содержит сырые байты ответа. Именно с ним мы и будем работать, так как он предоставляет удобные методы для чтения многобайтовых чисел.
Использование узла Function для парсинга
// msg.payload от узла Modbus-Read приходит как объект { data: Array, buffer: Buffer }
// Нас интересует именно buffer.
// Проверяем, что в payload действительно есть буфер нужной длины (4 байта для Float32)
if (msg.payload && msg.payload.buffer && msg.payload.buffer.length === 4) {
// Используем метод readFloatBE() для чтения 32-битного числа
// с плавающей запятой в формате Big-Endian (старший байт первый).
// Большинство Modbus-устройств используют именно этот порядок байт.
const voltage = msg.payload.buffer.readFloatBE(0);
// Округляем до двух знаков после запятой для удобства
msg.payload = parseFloat(voltage.toFixed(2));
// Обновляем статус узла для наглядности
node.status({ fill: "green", shape: "dot", text: "V: " + msg.payload + " V" });
return msg;
} else {
// Если пришли некорректные данные, логируем ошибку и останавливаем поток
node.status({ fill: "red", shape: "ring", text: "Invalid data" });
node.error("Invalid or empty buffer received from Modbus", msg);
return null; // Прерываем выполнение потока
}
Чтение нескольких параметров
Чтобы прочитать сразу несколько параметров (напряжение, ток, мощность), нам нужно изменить настройки `Modbus-Read`:
- Address: `0` (адрес первого параметра, напряжения).
- Quantity: `14`. Почему 14? Потому что ток находится по адресу 6, а мощность по адресу 12. Чтобы захватить все три параметра одним запросом, нам нужно прочитать всё от адреса 0 до (12+2)-1, то есть с 0 по 13 включительно. Всего 14 регистров.
Теперь код в узле `Function` будет сложнее, так как нам нужно читать значения из разных частей буфера:
// msg.payload.buffer теперь будет содержать 14 * 2 = 28 байт.
// Проверяем, что в payload действительно есть буфер нужной длины
if (msg.payload && msg.payload.buffer && msg.payload.buffer.length >= 28) {
const buffer = msg.payload.buffer;
// Смещение (offset) в байтах, а не в регистрах!
// 1 регистр = 2 байта.
const voltage = buffer.readFloatBE(0); // Адрес 0 -> смещение 0*2 = 0
const current = buffer.readFloatBE(12); // Адрес 6 -> смещение 6*2 = 12
const power = buffer.readFloatBE(24); // Адрес 12 -> смещение 12*2 = 24
// Формируем новый msg.payload в виде структурированного объекта
msg.payload = {
voltage: parseFloat(voltage.toFixed(2)),
current: parseFloat(current.toFixed(3)),
power: parseFloat(power.toFixed(1))
};
node.status({ fill: "green", shape: "dot", text: `V: ${msg.payload.voltage}, A: ${msg.payload.current}, W: ${msg.payload.power}` });
return msg;
} else {
node.status({ fill: "red", shape: "ring", text: "Invalid data" });
node.error("Invalid buffer length received from Modbus", msg);
return null;
}
Этот код считывает три значения из одного большого буфера, используя смещения в байтах, рассчитанные на основе карты регистров.
---
Структурирование и отправка данных в MQTT
После того как мы успешно распарсили данные, последним шагом будет их отправка в систему верхнего уровня, например, в MQTT-брокер. Это позволит другим сервисам (дашбордам, системам аналитики, правилам автоматизации) использовать полученные данные.
Преобразование в единый JSON-объект
Наш узел `Function` из предыдущего шага уже формирует `msg.payload` в виде удобного JSON-объекта. Это идеальный формат для передачи структурированных данных.
Пример `msg.payload` на выходе из `Function`-узла:
{
"voltage": 231.5,
"current": 1.453,
"power": 335.2
}
Если бы нам нужно было добавить еще данные, например, суммарную потребленную энергию (которую мы прочитали бы отдельным запросом к регистру 342), мы бы объединили их в один объект, используя узел `Join` или дополнительную логику в `Function`-узле.
Публикация данных с помощью MQTT Out
* Server: Выберите ваш MQTT-брокер (или создайте новую конфигурацию, если это необходимо, указав IP-адрес/хост, порт `1883` и, если требуется, логин/пароль).
* Topic: Укажите топик для публикации. Крайне важно использовать осмысленную и иерархическую структуру топиков.
* QoS: `1` (гарантирует доставку сообщения как минимум один раз).
* Retain: `true`. Это важно для телеметрии. Брокер сохранит последнее сообщение в этом топике. Когда новый клиент подпишется на него, он немедленно получит последнее актуальное состояние счетчика, не дожидаясь следующего цикла опроса.
* Name: `Publish Energy State`.
Рекомендации по именованию топиков
Правильная структура топиков — залог масштабируемости вашей системы. Рекомендуется следующий формат:
`{project_name}/{location}/{device_class}/{device_name}/state`
Пример для нашего случая:
`hi/office-floor1/meters/main-power/state`
- `hi`: Название проекта.
- `office-floor1`: Расположение объекта.
- `meters`: Класс устройства.
- `main-power`: Имя конкретного устройства.
- `state`: Суффикс, обозначающий, что этот топик содержит полное состояние устройства.
Теперь, развернув проект, вы сможете подписаться на этот топик любым MQTT-клиентом и видеть поступающие JSON-сообщения от вашего счетчика каждые 10 секунд.
---
Итоги и дальнейшие шаги
тоги и дальнейшие шаги
В этом уроке мы прошли полный путь от физического подключения устройства до интеграции его данных в централизованную систему. Это фундаментальный навык для любого инженера по автоматизации.
Обзор созданного потока и Ops-слоя
Наш итоговый поток в Node-RED имеет простую и логичную структуру:
`Inject` (для периодического запуска) -> `Modbus-Read` (опрос устройства) -> `Function` (парсинг и структурирование данных) -> `MQTT Out` (публикация).
Для обеспечения надежности (Operations layer) мы добавили механизмы обработки исключений:
Полученные навыки
По завершении этого урока вы научились:
- Самостоятельно подключать Modbus RTU устройства к контроллеру HI.
- Настраивать узлы `node-red-contrib-modbus` для опроса регистров.
- Читать техническую документацию на устройства (карту регистров) и применять ее на практике.
- Писать код в `Function`-узле для парсинга бинарных данных из Buffer в понятный JSON.
- Создавать отказоустойчивые потоки с использованием узла Catch для логирования ошибок связи.
- Публиковать структурированные данные и сообщения об ошибках в MQTT.
Идеи для дальнейшего развития
Созданный нами поток — это только начало. На его основе можно построить более сложные системы:
- Визуализация: Используйте данные из MQTT для построения графиков в `Node-RED Dashboard` или Grafana.
- Система оповещений: Добавьте узел `Switch` после парсера для проверки лимитов напряжения (207-253 В). При выходе за границы — уведомление в Telegram.
- Аналитика потребления: Сохраняйте данные по энергии в базу данных MySQL на контроллере для построения отчетов о суточном потреблении.
Задание для самостоятельной работы
В качестве закрепления материала попробуйте модифицировать созданный поток: добавьте чтение дополнительных параметров со счетчика, например, частоты сети (Frequency) по адресу `70` и коэффициента мощности (Power Factor) по адресу `30`. Добавьте эти значения в итоговый JSON-объект. Не забудьте убедиться, что ваш узел `Catch` корректно обрабатывает ошибки опроса новых регистров, если устройство временно отключится от шины.