ГлавнаяАкадемияNode-RED: установка, flows, msg/JSON, отладка → Практика: Опрос Modbus-счетчика электроэнергии

Практика: Опрос Modbus-счетчика электроэнергии

Урок 6 · Node-RED: установка, flows, msg/JSON, отладка · 30 мин · theory

Постановка задачи и обзор оборудования

остановка задачи и обзор оборудования

Цель этого урока — разработать в Node-RED полноценное решение для сбора данных с промышленного счетчика электроэнергии. Мы научимся настраивать периодический опрос устройства по шине RS-485, считывать его ключевые показания — напряжение, ток, активную мощность и суммарное потребление — а затем обрабатывать полученные данные и публиковать их в систему мониторинга через протокол MQTT. Этот навык является основой для построения систем учета энергоресурсов, мониторинга нагрузки и предиктивного обслуживания оборудования.

> 💡 Подсказка: Всегда начинайте работу с новым Modbus-устройством с изучения его карты регистров. Это сэкономит часы отладки. Обратите внимание на тип данных (Float, Int32), порядок байт (BE/LE) и смещение адресов (часто адреса в документации начинаются с 1, а в запросе нужно указывать 0).

Обзор типового оборудования

В качестве примера мы будем использовать распространенный однофазный счетчик SDM120-Modbus. Это компактное устройство, монтируемое на DIN-рейку, идеально подходящее для задач субучета в офисах, квартирах или для мониторинга отдельных производственных линий.

Ключевые характеристики:

Карта регистров 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-слой

Наше решение будет состоять из следующих логических блоков, которые мы последовательно реализуем:

  • Физический уровень: Счетчик SDM120 подключается к порту RS-485 контроллера HI с помощью двухпроводной линии.
  • Уровень драйвера (Node-RED): Узел `Modbus-Read` будет отправлять запросы на шину RS-485 по расписанию.
  • Слой эксплуатации (Ops-слой): Мы настроим узел `Catch` для перехвата системных ошибок (таймаутов, обрывов связи) от Modbus-узлов. Ошибки будут отправляться в системный лог и отдельный отладочный MQTT-топик, чтобы оператор узнал о проблеме со связью мгновенно.
  • Уровень обработки данных (Node-RED): Узел `Function` получит сырой ответ от счетчика, выполнит парсинг данных из бинарного буфера в JSON-объект.
  • Уровень интеграции (Node-RED): Узел `MQTT Out` опубликует данные в топик брокера для систем мониторинга или дашбордов.
  • Так мы построим надежный и масштабируемый поток телеметрии от полевого устройства до центральной системы с автоматическим контролем состояния связи.

    Физическое подключение и настройка шины RS-485

    Корректное физическое подключение — залог стабильной работы всей системы. Ошибки на этом этапе являются наиболее частой причиной проблем со связью.

    > ⚠️ Внимание: Неправильное подключение линий A/B не выведет из строя оборудование, но связь работать не будет. Самая частая ошибка — перепутанные клеммы. Если данные не читаются, первое, что нужно сделать — поменять A и B местами.

    Схема подключения

    Для подключения счетчика к контроллеру HI используется кабель "витая пара".

    Пример 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 и счетчики SDM, могут иметь встроенный терминатор, который активируется переключателем или через меню.

    Настройка параметров на счетчике

    Перед подключением к Node-RED необходимо убедиться, что параметры связи на самом счетчике установлены правильно. Для SDM120 это делается через его меню.

  • Slave ID (Unit-ID): Уникальный адрес устройства в сети Modbus. Зададим ему адрес `1`.
  • Baud Rate (Скорость): Скорость передачи данных. Стандартное значение — `9600`.
  • Parity (Четность): Метод проверки ошибок. Обычно `None`.
  • Data Bits / Stop Bits: Как правило, `8` и `1`.
  • Таким образом, наши параметры шины: 9600 8N1. Эти же значения мы будем использовать при настройке клиента в Node-RED.

    Идентификация порта в Linux

    Контроллер HI работает под управлением Debian. Физические порты RS-485 представлены в системе как файлы устройств в директории `/dev/`. Нам нужно определить, какой файл соответствует нашему порту.

    Самый простой способ найти порт — выполнить команду в терминале контроллера до и после подключения адаптера:

    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-клиента

  • Перетащите на холст узел `Modbus-Read`. Дважды кликните по нему, чтобы открыть окно настроек.
  • В поле `Server` вы увидите опцию "Add new Modbus-Client...". Нажмите на иконку карандаша справа, чтобы создать новую конфигурацию.
  • Откроется окно `Modbus Client properties`. Здесь мы настраиваем подключение к шине:
  • * 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: Это поле можно оставить пустым, так как мы будем указывать адрес для каждого запроса отдельно.

  • Нажмите `Add`, чтобы сохранить конфигурацию клиента. Теперь этот клиент можно будет выбирать во всех Modbus-узлах вашего проекта.
  • Настройка узла Modbus-Read

    Вернемся в окно настроек узла `Modbus-Read`. Теперь нам нужно указать, что именно мы хотим прочитать.

  • Server: Убедитесь, что выбран наш созданный клиент `RS485-Bus-1`.
  • Unit-ID: Укажите адрес нашего счетчика: `1`.
  • FC (Function Code): Выберите `FC 4: Read Input Registers`, так как согласно документации, данные счетчика находятся в регистрах этого типа.
  • Address: Это адрес первого регистра, с которого мы начинаем чтение. Чтобы прочитать напряжение, укажем `0`.
  • Quantity: Количество 16-битных регистров, которые нужно прочитать. Так как напряжение занимает 2 регистра (32-бит Float), укажем `2`.
  • Poll Rate: Частота опроса. Установим `10` секунд для начала.
  • Name: Дайте узлу имя, например, "Read Voltage".
  • Нажмите `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]

    }

    }

    Использование узла Function для парсинга

  • Добавьте на холст узел `Function` и соедините его с выходом `Modbus-Read`.
  • Дважды кликните по `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`:

    Теперь код в узле `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

  • Добавьте узел `MQTT Out` на холст и соедините его с выходом нашего `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`

    Теперь, развернув проект, вы сможете подписаться на этот топик любым MQTT-клиентом и видеть поступающие JSON-сообщения от вашего счетчика каждые 10 секунд.

    ---

    Итоги и дальнейшие шаги

    тоги и дальнейшие шаги

    В этом уроке мы прошли полный путь от физического подключения устройства до интеграции его данных в централизованную систему. Это фундаментальный навык для любого инженера по автоматизации.

    Обзор созданного потока и Ops-слоя

    Наш итоговый поток в Node-RED имеет простую и логичную структуру:

    `Inject` (для периодического запуска) -> `Modbus-Read` (опрос устройства) -> `Function` (парсинг и структурирование данных) -> `MQTT Out` (публикация).

    Для обеспечения надежности (Operations layer) мы добавили механизмы обработки исключений:

  • Узел `Catch`: Настроен на перехват ошибок конкретно от узла `Modbus-Read`. Это позволяет фиксировать таймауты или обрывы связи без остановки всего flow.
  • Обработка ошибок: Выход узла `Catch` соединен с `Debug` (вкладка Status) и вторым узлом `MQTT Out` в топик `telemetry/error/meter`, что позволяет оперативно узнать о проблемах с "железом" через центральную систему мониторинга.
  • Полученные навыки

    По завершении этого урока вы научились:

    Идеи для дальнейшего развития

    Созданный нами поток — это только начало. На его основе можно построить более сложные системы:

    Задание для самостоятельной работы

    В качестве закрепления материала попробуйте модифицировать созданный поток: добавьте чтение дополнительных параметров со счетчика, например, частоты сети (Frequency) по адресу `70` и коэффициента мощности (Power Factor) по адресу `30`. Добавьте эти значения в итоговый JSON-объект. Не забудьте убедиться, что ваш узел `Catch` корректно обрабатывает ошибки опроса новых регистров, если устройство временно отключится от шины.