Добрый день коллеги, поговорим о прозрачном конверторе Modbus TCP в Modbus RTU.

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

Мы предложим свободно программируемый контроллер AntexGate с помощью которого можно получить конвертер Modbus tcp – Modbus rtu – can – profitnet – bacnet – mqtt – http – opc UA – другой протокол.

Итак, рассмотрим первую связку Modbus TCP – Modbus RTU, нам понадобится сам контроллер AntexGate либо другой компьютер с linux/windows на борту и периферией RS485.

Первое, что необходимо – это установить программный продукт Node-Red, это делается одним скриптом:

bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)

Сделать Node-red после установки сервисом:

sudo systemctl enable nodered.service

Перезагружаем железяку:

sudo reboot

Теперь попадаем в среду нашего программного конвертора Node-red через web браузер по ссылке ip шлюза:1880

Далее настроим скрипт, который конвертирует посылку Modbus TCP в RTU, посылает в RS485 порт, ждет ответ и тут же преобразует ответ обратно в Modbus TCP.

Заходим в "меню" > "импорт"

Проект конвертора Modbus TCP <> RTU
[
    {
        "id": "22ff3060.54da6",
        "type": "function",
        "z": "de082ac2.4b8bf8",
        "name": "Convert TCP to RTU",
        "func": "var MBAPHeader = [5];\nvar crc = 0xFFFF;\n\nMBAPHeader[0]= msg.payload[0];\nMBAPHeader[1]= msg.payload[1];\nMBAPHeader[2]= msg.payload[2];\nMBAPHeader[3]= msg.payload[3];\nMBAPHeader[4]= msg.payload[4];\nflow.set('MBAP_HEADER', MBAPHeader);\n\nvar ReqLen = msg.payload[5];\nvar MBLen = (msg.payload.length)-ReqLen;\nvar MBReq = [MBLen];\n\nfor (let x=0; x < MBLen;x++){\n  MBReq[x] = msg.payload[x+ReqLen];\n}\n\nfor (var req = 0; req < ReqLen; req++) {\n  crc ^= MBReq[req];          // XOR byte into least sig. byte of crc\n  \n  for (var i = 8; i !== 0; i--) {    // Loop over each bit\n    if ((crc & 0x0001) !== 0) {      // If the LSB is set\n        crc >>= 1;                   // Shift right and XOR 0xA001\n        crc ^= 0xA001;\n    } else {                          // Else LSB is not set\n      crc >>= 1;                     // Just shift right\n    }\n  }\n}\n\nvar MyCRC = crc.toString(16);\nMBReq[MBLen+1] = parseInt(MyCRC.substring(0,2),16);\nMBReq[MBLen] = parseInt(MyCRC.substring(2,4),16);\n\nvar buf = new Buffer(MBReq);\nmsg.payload = buf;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 300,
        "y": 100,
        "wires": [
            [
                "7e62bbbc.bd8ea4"
            ]
        ],
        "outputLabels": [
            "XO"
        ]
    },
    {
        "id": "7e62bbbc.bd8ea4",
        "type": "serial request",
        "z": "de082ac2.4b8bf8",
        "name": "",
        "serial": "27dabad398437b7e",
        "x": 490,
        "y": 100,
        "wires": [
            [
                "db79ee80.1d792",
                "9e53f02d0a4e04fb"
            ]
        ]
    },
    {
        "id": "db79ee80.1d792",
        "type": "function",
        "z": "de082ac2.4b8bf8",
        "name": "Convert RTU to TCP",
        "func": "if (msg.payload.length > 0) {\n  var resLen = msg.payload.length - 2;\n  var respond = [];\n  respond[0] = flow.get('MBAP_HEADER')[0];\n  respond[1] = flow.get('MBAP_HEADER')[1];\n  respond[2] = flow.get('MBAP_HEADER')[2];\n  respond[3] = flow.get('MBAP_HEADER')[3];\n  respond[4] = flow.get('MBAP_HEADER')[4];\n  respond[5] = resLen;\n\n  for (let req = 6; req < resLen+6; req++) {\n    respond[req]= msg.payload[req-6];\n  }\n  msg.payload = new Buffer(respond);\n  return msg;\n} else {\n   return null;\n}\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 640,
        "y": 60,
        "wires": [
            [
                "2622a1e893f52bed",
                "140a62f5366490d9"
            ]
        ]
    },
    {
        "id": "55423964225fa869",
        "type": "comment",
        "z": "de082ac2.4b8bf8",
        "name": "Convert Modbus TCP to Modbus RTU",
        "info": "",
        "x": 170,
        "y": 40,
        "wires": []
    },
    {
        "id": "cbd6e86861af643d",
        "type": "tcp in",
        "z": "de082ac2.4b8bf8",
        "name": "Modbus TCP - IN",
        "server": "server",
        "host": "",
        "port": "5002",
        "datamode": "stream",
        "datatype": "buffer",
        "newline": "",
        "topic": "",
        "trim": false,
        "base64": false,
        "tls": "",
        "x": 100,
        "y": 100,
        "wires": [
            [
                "22ff3060.54da6",
                "48471bdeae3a1d30"
            ]
        ]
    },
    {
        "id": "2622a1e893f52bed",
        "type": "tcp out",
        "z": "de082ac2.4b8bf8",
        "name": "Modbus TCP - OUT",
        "host": "127.0.0.1",
        "port": "502",
        "beserver": "reply",
        "base64": false,
        "end": false,
        "x": 850,
        "y": 100,
        "wires": []
    },
    {
        "id": "9e53f02d0a4e04fb",
        "type": "debug",
        "z": "de082ac2.4b8bf8",
        "name": "debug 17",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 640,
        "y": 220,
        "wires": []
    },
    {
        "id": "140a62f5366490d9",
        "type": "debug",
        "z": "de082ac2.4b8bf8",
        "name": "debug 18",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 820,
        "y": 200,
        "wires": []
    },
    {
        "id": "48471bdeae3a1d30",
        "type": "debug",
        "z": "de082ac2.4b8bf8",
        "name": "debug 19",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 260,
        "y": 200,
        "wires": []
    },
    {
        "id": "27dabad398437b7e",
        "type": "serial-port",
        "serialport": "/dev/ttyUSB0",
        "serialbaud": "115200",
        "databits": "8",
        "parity": "none",
        "stopbits": "1",
        "waitfor": "",
        "dtr": "none",
        "rts": "none",
        "cts": "none",
        "dsr": "none",
        "newline": "5",
        "bin": "bin",
        "out": "time",
        "addchar": "",
        "responsetimeout": "1000"
    }
]

Копируем скрипт, нажимаем "Импорт"

 

Настраиваем порт для просушки в узле Modbus TCP IN, рекомендуем не использовать стандартный 502, а использовать нестандартный порт более 1024, для них не нужны дополнительные разрешения в Linux.

Настраиваем последовательный порт RS485/232 на скорость конечного железа, поддерживающего протокол Modbus RTU.

Жмем "Развернуть" и спустя 20 минут наш конвертер готов!

Настраиваем Ваш инструмент верхнего уровня, например Modbus TCP OPC сервер стандартно: ip_адре_ шлюза:5002:id_RTU_устройства.

Все работает стабильно - чтение и запись! Посылки как видим между запросом и ответом 100-200мс, что равно пингу. Удаленный шлюз AntexGate работает на встроенном LTE модеме.

Замер пинга до шлюза, доступ через VPN WireGuard.
Замер пинга до шлюза, доступ через VPN WireGuard.

Бонусом, для тех, у кого софт верхнего уровня поддерживает функцию Modbus RTU поверх TCP включаем это правило.

В Node-Red упрощаем поток и исключаем функции RTU TCP обработки, и наш проект в Node-Red становится без единой строчки кода.

Контроллер AntexGate в связке с Node-red является отличным универсальным решением для обработки, пересылки и хранения данных. Прошу прощения за рекламу, однако выше представленный проект Вы можете реализовать на любом железе и любой ОС.

Присоединяйтесь к нашему сообществу в Telegram

Железо можно получить на тесты, пишите на почту: info@antexcloud.ru (на три месяца для ЮР лиц и ИП)

Комментарии (8)


  1. hooperer
    11.06.2024 08:20
    +1

    Достаточно странно что на странице контроллера нет его цены и она выдаётся только по запросу.


    1. antex_dom Автор
      11.06.2024 08:20
      +1

      Огромная кнопка Прайс-лист Вас не смущает? Давайте продублирую https://antexcloud.ru/price/


      1. hooperer
        11.06.2024 08:20

        простите,

        вот сразу и не нашёл. нажал на кнопку " купить" которая левее прайс-листа и попал на какую то анкету обратной формы с запросами.

        почему-то ожидал увидеть цену/диапазон на странице прибора, и прайс лист не идентифицировался как возможность узнать цену, а кнопка " купить" идентифицировалась как возможность узнать цену.

        ну это у меня)


    1. antex_dom Автор
      11.06.2024 08:20
      +1


  1. Yak52
    11.06.2024 08:20

    Жмем "Развернуть" и спустя 20 минут наш конвертер готов!

    Если не секрет - это слабость железа или ... ?


    1. antex_dom Автор
      11.06.2024 08:20
      +1

      Установить софт, и настроить его просто рука быстрее мышку не двигает. Наверное не правильно выразится 20 минут это вся настройка в целом начиная от включения контроллера, поток разворачивается примерно 500мс.


  1. AlloySteel
    11.06.2024 08:20

    Молодцы! Какое замечательное решение в одной коробочке! За Node Red отдельный плюс!


  1. antex_dom Автор
    11.06.2024 08:20

    Спасибо Вам, за такой коммент, после таких слов хочется еще лучше коробочки делать!