четверг, 7 октября 2021 г.

ModbusTCP терминал на C# своими руками

 

При изучении протокола Modbus (да и вообще, в принципе, наверное, любого другого протокола), чтобы понять структуру посылаемых данных, очень полезно самому составить эту структуру и отослать ее устройству которое будет принимать эти данные. И для этих целей очень удобно пользоваться терминалом - где ты можешь посылать запросы и принимать ответы, и видеть все это на своем экране. Если почитать про протокол Modbus RTU, то там написано что структура посылки выглядит следующим образом: 

{MbAddr} {CmdCode} {AddrH} {AddrL} {QntH} {QntL} {CRCL} {CRCH}

То есть внутри каждой фигурной скобки находится 1 байт, который может принимать значение от 0 до 255 или в шестнадцатеричном формате FF 

Таким образом чтобы сформировать запрос к ведомому устройству, нужно через терминал отправить что-то вроде этого 01 03 00 14 00 02 84 0F и если запрос сформирован верно то в ответ должно прийти что-то тоже вполне осмысленное, хотя и состоящее также из наборов байт



В этом наборе байт по порядку 01 - адрес ведомого устройства, 03 - код команды на чтение регистров хранения (Holding Register или Analog Output), 00 14 - адрес регистра с которого начинается чтение, 00 02 - количество читаемых регистров, 84 0F - это контрольная сумма

Как видим в ответ пришла телеграмма, тоже состоящая из байтов, и в ней выделенное значение при переводе в формат числа с плавающей точкой равно ожидаемому 0,1121 - это значение тока которое измеряет наше устройство. В конце есть так же контрольная сумма и она совпадает с ожидаемой. В данном случае для проверки использовался терминал Termite, который предназначен для тестирования устройств с Modbus RTU. Эта программа автоматически считает контрольную сумму, что очень удобно.

Сейчас же мы будем делать свой терминал для Modbus TCP, конечно же не такую навороченную и красивую программу, но зато вполне функциональную и при этом еще лучше поймем принцип этого протокола. Очень удобно и классно в этом случае, что в протоколе Modbus TCP не предусмотрена контрольная сумма, и все дело в том что работает он поверх TCP, в котором уже есть своя контрольная сумма и потому нет смысла дублировать. И очень удобно для нас и то, что мастер сети хоть и является мастером, но по отношению к ведомому устройству он все же клиент, а ведомое устройство по отношению к мастеру это сервер. Соответственно чтобы посылать Modbus запросы нам нужно написать обычный TCP клиент, который будет посылать нужную структуру байтов, и выводить в терминал полученную от сервера (ведомого устройства) структуру байт в окно. Проще всего это сделать в виде консольного приложения и в гугле находится очень много примеров для таких приложений, например у Microsoft.

Я скопировал данный код в блокнот, немного подправил и скомпилировал его через командную строку, в итоге увидел что запросы отправляются и принимаются


На этом скрине видно, что данные отправляются и принимаются уже в шестнадцатеричном формате. Это стало возможным путем преобразования отправляемых данных из hex string to byte array. А принимаемые данные наоборот из byte array to hex string. Первые ответы со stackoverflow отлично заработали. На скрине также видно, что строка дополняется нулями, и происходит это потому, что для получения ответа выделяется буфер с конкретным размером, и незаполненная данными часть массива остается заполненной нулями.

Теперь самое интересное - нужно дополнить программу так чтобы отправлять на устройство телеграмму в соответствии с протоколом ModbusTCP, а делается это тоже очень просто. В описании протокола сказано, что он состоит из 2 частей: заголовок и данные. Собственно заголовок не меняется, а данные нужно вводить с клавиатуры, потом все это сложить вместе и отправить ведомому устройству. В результате у меня появился вот такой код (постарался расписать комментарии):

 Static void Main(string[] args) {
      Console.WriteLine("Введите IP адрес ведомого устройства. Порт по умолчанию 502");
      string server = Console.ReadLine();
      int port = 502;

      while (true) {
        // Modbus TCP frame format = MBAP Header + PDU (Modbus Application Header + Protocol Data Unit)
        Console.WriteLine("Введите запрос для ведомого устройства");
        Console.WriteLine("Формат такой же как ModBus RTU, но без контрольной суммы, т.е. {MbAddr} {CmdCode} {AddrH} {AddrL} {QntH} {QntL}");
        string MBAP = "0001 0000 0006";
        // 0001 - Идентификатор транзакции    Transaction Identifier
        // 0000 - Идентификатор протокола    Protocol Identifier
        // 0006 - Длина (6 байтов идут следом)    Message Length
        string PDU = Console.ReadLine();
        // Считываем ввод PDU с клавиатуры. Формат такой же как ModBus RTU, но без контрольной суммы:
        // {MbAddr} {CmdCode} {AddrH} {AddrL} {QntH} {QntL}
        // например - 01 03 00 01 00 02, где 01- адрес ведомого устройства
        // 03 - код команды чтение регистров хранения (Holding Register или Analog Output),
        // 00 01 - адрес регистра с которого начинается чтение
        // 00 02 - количество читаемых регистров

        string SendToDevice = MBAP + PDU;
        Connect(server, port, SendToDevice.Replace(" ", ""));
      }
    }

Для проверки работоспособности использовалась программа эмулятор Modbus-Slave и на первом скрине хорошо видно, что запрашиваемые данные которые лежат в регистрах Slave устройства соответствуют тем что мы получаем в терминале. Конечно же у этого терминала очень много недостатков - нет валидации ввода, и при любой внештатной ситуации он будет сразу же ломаться, но тем не менее он все же работает и выполняет возложенные на него функции. Полнай код программы лежит в моем гитхабе: ModbusTCP-terminal

Ну и еще кое-что. Эту программу можно довольно легко изменить и перекомпиллировать у себя на компьтере без установки тяжелой Visual Studio, нужет только .NET Framework и блокнот (кторое  скорее всего уже установлены у вас). Сохранить весь код программы в текстовый фал с расширением .cs и запустить команду компиляции. Для удобства я добавил в контекстное меню .cs файлов команду для компиляции при помощи реестра Windows:

 Содержимое файла compiler.reg

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\cs_auto_file\shell\Compile]
"MUIVerb"="Скомпилировать C#"
"Icon"="shell32.dll,80"

[HKEY_CLASSES_ROOT\cs_auto_file\shell\Compile\command]
@="cmd /k C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\csc.exe -nologo \"%1\""
Чтобы сделать так же, нужно сохранить текст выше в текстовый файл, переименовать его в compiler.reg и запустить. После этого у вас должно появится такое же контекстное меню что и у меня, и нажав на него, можно быстро скомпилировать программу Modbus терминала, а редактировать ее можно при помощи блокнота.

1 комментарий:

  1. Windows Registry Editor Version 5.00

    [HKEY_CLASSES_ROOT\.cs\shell\Compile]
    @="Compile C#"
    "Icon"="shell32.dll,80"

    [HKEY_CLASSES_ROOT\.cs\shell\Compile\command]
    @="cmd /k C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\csc.exe -nologo \"%1\""

    [HKEY_CLASSES_ROOT\SystemFileAssociations\.cs\shell\Compile]
    @="Compile C#"
    "Icon"="shell32.dll,80"

    [HKEY_CLASSES_ROOT\SystemFileAssociations\.cs\shell\Compile\command]
    @="cmd /k C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\csc.exe -nologo \"%1\""

    ОтветитьУдалить