Пользовательские протоколы/en — различия между версиями

Материал из WebHMI Wiki
Перейти к: навигация, поиск
(Новая страница: «There are a lot of different automation devices with non-standard communication protocols. To solve the problem of data collection from such devices in WebHMI, it…»)
 
(не показана одна промежуточная версия этого же участника)
Строка 1: Строка 1:
 +
<languages/>
 +
<languages/>
 
There are a lot of different automation devices with non-standard communication protocols. To solve the problem of data collection from such devices in WebHMI, it is possible to create custom protocols in Lua [https://ru.wikipedia.org/wiki/Lua]. This function is available in WebHMI since version 1.10.0.3420.
 
There are a lot of different automation devices with non-standard communication protocols. To solve the problem of data collection from such devices in WebHMI, it is possible to create custom protocols in Lua [https://ru.wikipedia.org/wiki/Lua]. This function is available in WebHMI since version 1.10.0.3420.
  
== О языке Lua ==
+
== About Lua ==
Lua является типичным процедурным языком программирования. Он предоставляет широкие возможности для объектно-ориентированной и функциональной разработки. Lua создавался как мощный и простой язык, обладающий всеми необходимыми выразительными средствами. С документацией по языку можно ознакомится на официальном сайте [http://www.lua.org/manual/5.1/]. Бегло ознакомится с синтаксисом можно на удобном сайте [http://tylerneylon.com/a/learn-lua/]. В WebHMI используется Lua версии 5.1.5.
+
Lua is a typical procedural programming language. It provides ample opportunities for object-oriented and functional development. Lua was created as a powerful and simple language with all the necessary expressive means. The documentation on the language can be found on the official website [http://www.lua.org/manual/5.1/]. You can easily get acquainted with the syntax on a convenient site [http://tylerneylon.com/a/learn-lua/]. WebHMI uses Lua version 5.1.5.
  
Почему именно Lua?
+
Why Lua?
  
Lua — язык, который специально создавался для встраивания в приложения, написанные на языке Си. Он обладает отличной производительностью, потребляет очень мало ресурсов и имеет богатые возможности.
+
Lua is a language that was specially created for embedding in applications written in C language. It has excellent performance, consumes very few resources and has rich capabilities.
  
== Общая концепция пользовательских протоколов ==
+
== General concept of custom protocols ==
В WebHMI минимальной единицей информации является регистр. В общем случае обмен данными со всеми устройствами происходит циклически – регистры, которые должны быть опрошены в данном скане, читаются один за одним. Запись в регистры также происходит по одному регистру за раз.  
+
In WebHMI, the minimum unit of information is the register. In general, data exchange with all devices occurs cyclically - the registers that should be polled in this scan are read one by one. Registers are also written one register at a time.  
  
WebHMI позволяет создать свой протокол и определить функции чтения и записи регистра. Эти функции должны сформировать запрос, отправить его устройству, принять от него ответ, разобрать его и, в зависимости от результата, вернуть необходимые данные.
+
WebHMI allows you to create your own protocol and define the  read and write functions for the register. These functions must form a request, send it to the device, take an answer from it, disassemble it and, depending on the result, return the necessary data.
  
== Создание протокола ==
+
== Creating a Protocol ==
Для перехода к списку пользовательских протоколов нажмите на кнопку "'''Custom protocols'''" на страние '''Setup->Registers'''.<br/>
+
To go to the list of user protocols, click on the button "'''Custom protocols'''" on the page'''Setup->Registers'''.<br/>
 
[[Файл:Custom-protocol-button.png|600px]]<br/>
 
[[Файл:Custom-protocol-button.png|600px]]<br/>
  
Вы попадете на страницу управления протоколами. В данном примере мы видим два демонстрационных протокола – ModBus TCP Demo и ModBus ASCII Demo:<br/>
+
You will be taken to the protocol management page. In this example, we see two demonstration protocols - ModBus TCP Demo and ModBus ASCII Demo:<br/>
 
[[Файл:Custom-protocol-list.png|600px]]<br/>
 
[[Файл:Custom-protocol-list.png|600px]]<br/>
  
Давайте посмотрим на страницу редактирования протокола ModBus TCP Demo:<br/>
+
Let's look at the page for editing the ModBus TCP Demo protocol:
[[Файл:Custom-protocol-demo.png|600px]]<br/>
+
  
Протоколу можно задать:
+
Followed are settings used for custom protocols:
  
* название, описание
+
* Title, description
* тип (TCP/IP или Serial)
+
* type (TCP/IP or Serial)
* сетевой порт по умолчанию (только для TCP)
+
* default TCP port (only for TCP)
* регулярное выражение для проверки допустимости вводимых адресов
+
* regular expression used for register address validation
* сообщение об ошибке, которе будет выводится при вводе неверного адреса регистра
+
* An error message that will be displayed when you enter an invalid register address
* код с программой для обработки протокола
+
* Code with a custom protocol
  
Регулярное выражение должно обеспечить проверку правильности адреса регистра на странице редактирования регистров (при выборе этого протокола). Пример:<br/>
+
The regular expression must ensure that the register address is validated on the register editing page (when this protocol is selected). Example:<br/>
 
[[Файл:Custom-protocol-validate.png|600px]]<br/>
 
[[Файл:Custom-protocol-validate.png|600px]]<br/>
  
Также мы видим удобный редактор кода. В нем поддерживается форматирование, подсветка и валидация синтаксиса. Так что писать код удобно.<br/>
+
We also see a convenient code editor. It supports formatting, highlighting and validation of syntax. So it's convenient to write the code <br/>
Если в коде допущена синтаксическая ошибка то в соответствующей строке появится красный крестик. Что бы увидеть детальное сообщение об ошибке достаточно навести на него курсор мышки:<br/>  
+
If there is a syntax error in the code, a red X appears in the corresponding line. To see a detailed error message, just point your mouse at it:<br/>  
 
[[Файл:Custom-protocol-error.png|600px]]<br/>
 
[[Файл:Custom-protocol-error.png|600px]]<br/>
  
После создания протокола он появится в выпадающем списке доступных '''PLC models''' на странице создания новых '''Connections''' и с ним можно будет работать так же как и с обычным встроенным протоколом. <br/>
+
After creating the protocol, it will appear in the drop-down list of available '''PLC models''' on the page of creating new'''Connections''' and with it you can work as well as with the usual built-in protocol
[[Файл:Custom-protocol-select.png|600px]]<br/>
+
  
== Необходимые функции ==
+
== Necessary functions ==
WebHMI ожидает увидеть во введенном коде три функции:
+
WebHMI expects to see three functions in the custom protocol code:
 
* createDevices
 
* createDevices
 
* readRegister
 
* readRegister
Строка 49: Строка 49:
  
 
== createDevices ==
 
== createDevices ==
Процедура '''createDevices''' вызывается один раз при старте WebHMI и создает именованные префиксы для адресов регистров. Что бы лучше понять это давайте рассмотрим пример для устройств ModBUS. Создадим типы регистров для Coils, Discrete Inputs, Holding Registers, Input Registers:
+
The procedure 'createDevices' is called once at the start of WebHMI and creates named prefixes for register addresses. To better understand this, let's look at an example for ModBUS devices. Let's create register types for the Coils, Discrete Inputs, Holding Registers, Input Registers:
  
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
Строка 60: Строка 60:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Здесь создается четыре типа регистров. Для такого протокола в строке адреса регистра можно будет указывать регистры вида C14, DI4, HR34355, IR145.  
+
There are four types of registers. For such a protocol, registers of the form C14, DI4, HR34355, IR145 can be specified in the register address line. The '''addDevice ''' procedure is called for each type of address. It is passed a table with such parameters:
Процедура '''addDevice''' вызывается для каждого такого типа адресов. Ей передается таблица с такими параметрами:
+
  
* name – строка-перфикс, именно эта часть в адресе будет определять дальнейшую обработку чтения/записи этого регистра
+
* name – String-prefix, it is this part in the address that will determine the further processing of the read / write of this register
* shift – константа, будет прибавлена к значению адреса регистра. Т.е. можно сделать что бы регистр с адресом D30 преобразовался в D1030, а D33 – в D1033 и т.п.
+
* shift – this will be added to the value of the register address. I.e. it can be done so that the register with address D30 is converted to (with shift = 1000) D1030, and D33 to D1033, etc.
* base – система счисления адреса. У некоторых устройств используются адреса в восьмиричной или шестнадцатиричных системах счислений
+
* base – address system. Some devices use addresses in octal or hexadecimal number systems
* xtraFields – набор дополнительных параметров (максимум 5 штук), будет передан в функции read/write.
+
* xtraFields – A set of additional parameters (maximum 5 pieces), will be transferred to the function read / write in xtraFields table.
  
 
== onScanStart ==
 
== onScanStart ==
Процедура '''onScanStart''' вызывается каждый раз при начале нового скана. Она может быть полезна для протоколов, где за один запрос читается массив значений для нескольких регистров. Для таких протоколов можно кешировать результат запроса и возвращать значения из кеша. Сброс кеша можно производить в процедуре onScanStart.
+
The '''onScanStart''' procedure is called every time a new scan starts. It can be useful for protocols where one array reads an array of values ​​for several registers. For such protocols, you can cache the result of the query and return values ​​from the cache. You can reset the cache in the onScanStart procedure.
  
onScanStart доступна в прошивках начиная с версии 2.1.3923.
+
onScanStart Available in firmware since version 2.1.3923.
  
 
== readRegister ==
 
== readRegister ==
Функция '''readRegister''' должна произвести чтение указанного регистра.
+
The function '''readRegister''' should read the specified register.
  
В случае успешного чтения функция '''readRegister''' должна вернуть массив байт, длина которого соответствует указанному типу данных (1, 2 или 4) или же число. В случае неудачи необходимо вернуть false.
+
In case of successful reading, the function '''readRegister '''should return an array of bytes, with length corresponding to the specified data type (1, 2 or 4) or the number. In case of failure, you must return false.
  
Ей в качестве аргументов передаются три параметра:
+
Three parameters are passed to it as arguments:
* reg - таблица (структура) с параметрами регистра
+
* reg - Table (structure) with register parameters
* device - таблица (структура) с параметрами данного типа регистра (те данные, которые определены в createDevices)
+
* device - Table (structure) with register type parameters  (that were defined in createDevices)
* unitId – ID устройства на шине или прочий ID. Примером может быть Slave ID в ModBus RTU или Unit ID в ModBus TCP.
+
* unitId – device ID for the bus or other ID. For instance, Slave ID in ModBus RTU or Unit ID in ModBus TCP.
  
В структуре '''reg''' есть такие атрибуты:
+
There are such attributes in the '''reg''' structure:
* internalAddr - пересчитанный адрес регистра. Это число, пересчитанное из указанной системы счисления с прибавлением к нему shift.
+
* internalAddr - Recalculated register address. This number is recalculated from the specified number system with shift added to it.
* addr - оригинальный адрес регистра, который ввел пользователь.
+
* addr - The original address of the register that the user entered.
* dataType – тип данных, который пользователь указал для регистра. 0 = Bit, 1 = Byte, 2 = Word, 3 = Double Word, 4 = UnixTime
+
* dataType – The type of data that the user specified for the register. 0 = Bit, 1 = Byte, 2 = Word, 3 = Double Word, 4 = UnixTime
  
В структуре '''device''' есть такие атрибуты:
+
There are such attributes in the '''device''' structure:
* shift - значение shift из соответствующей строки createDevices
+
* shift - The shift value from the corresponding row in createDevices
* base - значение base из соответствующей строки createDevices
+
* base - base value from the corresponding row in  createDevices
* xtraFields – значение xtraFields из соответствующей строки createDevices
+
* xtraFields – xtraFields value from the corresponing row in createDevices
  
Эти параметры передаются для того, что бы можно было правильно и полноценно составить запрос согласно протокола.
+
These parameters are passed in order to be able to correctly and fully compose a request according to the protocol.
  
 
Для передачи запроса устройству используются функции '''sendBytes''' и '''sendString'''. Для чтения ответа - '''readBytes''', '''readString'''.
 
Для передачи запроса устройству используются функции '''sendBytes''' и '''sendString'''. Для чтения ответа - '''readBytes''', '''readString'''.
Строка 244: Строка 243:
  
 
== writeRegister ==
 
== writeRegister ==
Функция '''writeRegister''' должна произвести запись нового значения в указанный регистр. При успешной записи она должна вернуть true. В случае ошибки - false.
+
The '''writeRegister''' function should write a new value to the specified register. If the record is successful, it should return true. In case of an error, false.
  
 
Ей передаются все те же параметры, что и для readRegister, а также дополнительно четвертый параметр - новое значение.
 
Ей передаются все те же параметры, что и для readRegister, а также дополнительно четвертый параметр - новое значение.
 +
It has all the same arguments as for readRegister, as well as an additional fourth argument, a new value.
  
Функция '''writeRegister''' может использовать те же способы записи и чтения массивов байт в порт.
+
The '''writeRegister''' function may use the same methods of reading from and writing bytes to the port.
  
Давайте рассмотрим пример этой функции для протокола ModBus TCP:
+
Let's have a look at the example of this function for ModBus TCP protocol:
  
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
Строка 427: Строка 427:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Примеры протоколов ==
+
== The examples of custom protocols ==
В качестве примера мы реализовали (частично) два протокола: [http://wiki.webhmi.com.ua/index.php/Пример_протокола_ModBus_TCP ModBus TCP] и [http://wiki.webhmi.com.ua/index.php/Пример_протокола_ModBus_ASCII ModBus ASCII].
+
As an example we've made (partually) several protocols:
 
+
*[http://wiki.webhmi.com.ua/index.php/Пример_протокола_ModBus_TCP ModBus TCP]  
На странице подключения к [http://wiki.webhmi.com.ua/index.php/%D0%A2%D0%B5%D0%BD%D0%B7%D0%BE%D0%B4200 тензометрическому контроллеру Тензод200] также есть пример пользовательского протокола.
+
*[http://wiki.webhmi.com.ua/index.php/Пример_протокола_ModBus_ASCII ModBus ASCII].
 +
*[http://wiki.webhmi.com.ua/index.php/Modbus_RTU_%D0%B2_%D0%B2%D0%B8%D0%B4%D0%B5_custom_protocol Modbus RTU].

Текущая версия на 14:49, 30 мая 2018

Другие языки:
English • ‎русский
Другие языки:
English • ‎русский

There are a lot of different automation devices with non-standard communication protocols. To solve the problem of data collection from such devices in WebHMI, it is possible to create custom protocols in Lua [1]. This function is available in WebHMI since version 1.10.0.3420.

About Lua

Lua is a typical procedural programming language. It provides ample opportunities for object-oriented and functional development. Lua was created as a powerful and simple language with all the necessary expressive means. The documentation on the language can be found on the official website [2]. You can easily get acquainted with the syntax on a convenient site [3]. WebHMI uses Lua version 5.1.5.

Why Lua?

Lua is a language that was specially created for embedding in applications written in C language. It has excellent performance, consumes very few resources and has rich capabilities.

General concept of custom protocols

In WebHMI, the minimum unit of information is the register. In general, data exchange with all devices occurs cyclically - the registers that should be polled in this scan are read one by one. Registers are also written one register at a time.

WebHMI allows you to create your own protocol and define the read and write functions for the register. These functions must form a request, send it to the device, take an answer from it, disassemble it and, depending on the result, return the necessary data.

Creating a Protocol

To go to the list of user protocols, click on the button "Custom protocols" on the pageSetup->Registers.
Custom-protocol-button.png

You will be taken to the protocol management page. In this example, we see two demonstration protocols - ModBus TCP Demo and ModBus ASCII Demo:
Custom-protocol-list.png

Let's look at the page for editing the ModBus TCP Demo protocol:

Followed are settings used for custom protocols:

  • Title, description
  • type (TCP/IP or Serial)
  • default TCP port (only for TCP)
  • regular expression used for register address validation
  • An error message that will be displayed when you enter an invalid register address
  • Code with a custom protocol

The regular expression must ensure that the register address is validated on the register editing page (when this protocol is selected). Example:
Custom-protocol-validate.png

We also see a convenient code editor. It supports formatting, highlighting and validation of syntax. So it's convenient to write the code
If there is a syntax error in the code, a red X appears in the corresponding line. To see a detailed error message, just point your mouse at it:
Custom-protocol-error.png

After creating the protocol, it will appear in the drop-down list of available PLC models on the page of creating newConnections and with it you can work as well as with the usual built-in protocol

Necessary functions

WebHMI expects to see three functions in the custom protocol code:

  • createDevices
  • readRegister
  • writeRegister

createDevices

The procedure 'createDevices' is called once at the start of WebHMI and creates named prefixes for register addresses. To better understand this, let's look at an example for ModBUS devices. Let's create register types for the Coils, Discrete Inputs, Holding Registers, Input Registers:

function createDevices ()
  addDevice({name = "C",  shift = 0, base = 10, xtraFields = {1, 5}});
  addDevice({name = "DI", shift = 0, base = 10, xtraFields = {2, 0}});
  addDevice({name = "HR", shift = 0, base = 10, xtraFields = {3, 6, 16}});
  addDevice({name = "IR", shift = 0, base = 10, xtraFields = {4, 0}});
end

There are four types of registers. For such a protocol, registers of the form C14, DI4, HR34355, IR145 can be specified in the register address line. The addDevice procedure is called for each type of address. It is passed a table with such parameters:

  • name – String-prefix, it is this part in the address that will determine the further processing of the read / write of this register
  • shift – this will be added to the value of the register address. I.e. it can be done so that the register with address D30 is converted to (with shift = 1000) D1030, and D33 to D1033, etc.
  • base – address system. Some devices use addresses in octal or hexadecimal number systems
  • xtraFields – A set of additional parameters (maximum 5 pieces), will be transferred to the function read / write in xtraFields table.

onScanStart

The onScanStart procedure is called every time a new scan starts. It can be useful for protocols where one array reads an array of values ​​for several registers. For such protocols, you can cache the result of the query and return values ​​from the cache. You can reset the cache in the onScanStart procedure.

onScanStart Available in firmware since version 2.1.3923.

readRegister

The function readRegister should read the specified register.

In case of successful reading, the function readRegister should return an array of bytes, with length corresponding to the specified data type (1, 2 or 4) or the number. In case of failure, you must return false.

Three parameters are passed to it as arguments:

  • reg - Table (structure) with register parameters
  • device - Table (structure) with register type parameters (that were defined in createDevices)
  • unitId – device ID for the bus or other ID. For instance, Slave ID in ModBus RTU or Unit ID in ModBus TCP.

There are such attributes in the reg structure:

  • internalAddr - Recalculated register address. This number is recalculated from the specified number system with shift added to it.
  • addr - The original address of the register that the user entered.
  • dataType – The type of data that the user specified for the register. 0 = Bit, 1 = Byte, 2 = Word, 3 = Double Word, 4 = UnixTime

There are such attributes in the device structure:

  • shift - The shift value from the corresponding row in createDevices
  • base - base value from the corresponding row in createDevices
  • xtraFields – xtraFields value from the corresponing row in createDevices

These parameters are passed in order to be able to correctly and fully compose a request according to the protocol.

Для передачи запроса устройству используются функции sendBytes и sendString. Для чтения ответа - readBytes, readString.

На вход sendBytes принимает таблицу (массив) байт. Результатом будет true в случае успеха и false в случае ошибки.
На вход sendString принимает строку. Результатом будет true в случае успеха и false в случае ошибки.
На вход readBytes принимает количество байт, которое необходимо прочитать. Результатом будет таблица (массив) байт в случае успеха и false в случае ошибки.
На вход readString принимает количество байт, которое необходимо прочитать. Результатом будет строка в случае успеха и false в случае ошибки.

Если необходимо закрыть соединение (например, в случае множественных ошибок), то можно вызвать процедуру closeConnection.

Если необходимо сделать паузу, то можно вызвать функцию sleep. Ее единственным аргументом должно быть время в микросекундах. Пример: sleep(20000); - произойдет пауза 20 миллисекунд.

Для работы с битами можно использовать библиотеку bitop [4].

Для отладки и вывода диагностических сообщений можно использовать процедуры ERROR, INFO, DEBUG и TRACE которые доступны и в обычных сценариях Lua [5].

Что бы лучше понять как это все работает давайте рассмотрим пример функции readRegister для протокола ModBus TCP:

local transId = 0; 
local errorCount = 0;

function readRegister (reg, device, unitId)

  local request = {};
  
  -- transaction ID
  transId = transId + 1;
  
  request[1] = bit.band(bit.rshift(transId, 8), 255);
  request[2] = bit.band(transId, 255);

  -- protocol ID
  request[3] = 0;
  request[4] = 0;
  
  -- message length
  request[5] = 0;
  request[6] = 6;
  
  -- unit ID
  request[7] = unitId;
  
  -- function code
  request[8] = device.xtraFields[1];
  
  -- address of register
  request[9] = bit.band(bit.rshift(reg.internalAddr, 8), 255);
  request[10] = bit.band(reg.internalAddr, 255);

  -- count of registers
  request[11] = 0;
  request[12] = 1;
  
  if (reg.dataType == 3) then -- double word
    request[12] = 2;
  end
  
  local res = sendBytes(request);
  
  if (res == false) then
      DEBUG("Can't send bytes");
      return false;
  end

  local response = {};
  -- read MBAP Header
  response = readBytes(7);
  if (response == false) then
      errorCount = errorCount + 1;
      if (errorCount > 3) then
          closeConnection();
          errorCount = 0;
      end
      DEBUG("Can't read MBAP");
      return false;
  end
  res = #response;

  if (res ~= 7) then
      errorCount = errorCount + 1;
      if (errorCount > 3) then
          closeConnection();
          errorCount = 0;
      end
      DEBUG("Can't read MBAP");
      return false;
  end

  if (response[1] ~= request[1] or response[2] ~= request[2]) then
      ERROR("Wrong transaction ID. Got #" .. (response[1] * 256 + response[2]) .. " but expected #" .. (request[1] * 256 + request[2]));
      return false;
  end
      
  if (response[3] ~= request[3] or response[4] ~= request[4]) then
      ERROR("Wrong protocol");
      return false;
  end

  if (response[7] ~= request[7]) then
      ERROR("Wrong UnitID in response");
      return false;
  end

  local length = response[5] * 256 + response[6];

  if (length < 1) then
      ERROR("Wrong length in response");
      return false;
  end
  
  local responsePDU = {};
  -- read MBAP Header

  responsePDU = readBytes(length - 1);
  if (responsePDU == false) then
      errorCount = errorCount + 1;
      if (errorCount > 3) then
          closeConnection();
          errorCount = 0;
      end
      DEBUG("Can't read PDU in response");
      return false;
  end
  res = #responsePDU;

  if (responsePDU[1] ~= request[8]) then
      ERROR("Wrong function in response");
      return false;
  end

  local dataLength = responsePDU[2];
  if (dataLength ~= length - 3) then
      ERROR("Wrong length in PDU");
      return false;
  end
  
  local result = {};

  if (dataLength >= 1) then
      for i = 1, dataLength do
          result[i] = responsePDU[2 + i];
      end
  end

  return result;
end

writeRegister

The writeRegister function should write a new value to the specified register. If the record is successful, it should return true. In case of an error, false.

Ей передаются все те же параметры, что и для readRegister, а также дополнительно четвертый параметр - новое значение. It has all the same arguments as for readRegister, as well as an additional fourth argument, a new value.

The writeRegister function may use the same methods of reading from and writing bytes to the port.

Let's have a look at the example of this function for ModBus TCP protocol:

function writeRegister (reg, device, unitId, newValue)
    local request = {};
    
    transId = transId + 1;
    -- transaction ID
    request[1] = bit.band(bit.rshift(transId, 8), 255);
    request[2] = bit.band(transId, 255);
    
    -- protocol ID
    request[3] = 0;
    request[4] = 0;

    if (reg.dataType == 3) then -- double word
        -- message length
        request[5] = 0;
        request[6] = 11;
        
        -- unit ID
        request[7] = unitId;
        
        -- function code
        request[8] = device.xtraFields[3];
        
        -- address of register
        request[9] = bit.band(bit.rshift(reg.internalAddr, 8), 255);
        request[10] = bit.band(reg.internalAddr, 255);

        -- count of registers
        request[11] = 0;
        request[12] = 2;
        
        -- bytes with data
        request[13] = 4;

        -- value of registers
        request[14] = bit.band(bit.rshift(newValue, 24), 255);
        request[15] = bit.band(bit.rshift(newValue, 16), 255);
        request[16] = bit.band(bit.rshift(newValue, 8), 255);
        request[17] = bit.band(newValue, 255);
      
        local res = sendBytes(request);
  
        if (res == false) then
            DEBUG("Can't send bytes");
            return 0;
        end

        local response = {};

        response = readBytes(7);
        if (response == false) then
          DEBUG("Can't read response");
          return false;
        end
        res = #response;

        if (res ~= 7) then
          DEBUG("Wrong response length");
          return false;
        end
        
        if (response[1] ~= request[1] or response[2] ~= request[2]) then
          ERROR("Wrong transaction ID. Got #" .. (response[1] * 256 + response[2]) .. " but expected #" .. (request[1] * 256 + request[2]));
          return false;
        end
          
        if (response[3] ~= request[3] or response[4] ~= request[4]) then
          ERROR("Wrong protocol");
          return false;
        end
        
        if (response[7] ~= request[7]) then
          ERROR("Wrong UnitID in response");
          return false;
        end
        
        local length = response[5] * 256 + response[6];

        if (length < 1) then
          ERROR("Wrong length in response");
          return false;
        end
        
        local responsePDU = {};

        responsePDU = readBytes(length - 1);
        if (responsePDU == false) then
          DEBUG("Can't read response PDU");
          return false;
        end

        res = #responsePDU;
        
        if (responsePDU[1] ~= request[8]) then
          ERROR("Wrong function in response");
          return false;
        end
        
        if (responsePDU[2] ~= request[9] or responsePDU[3] ~= request[10]) then
          ERROR("Wrong register address in response");
          return false;
        end

        if (responsePDU[4] ~= 0 or responsePDU[5] ~= 2) then
          ERROR("Wrong register count in response");
          return false;
        end
    else
        if (device.xtraFields[2] == 0) then
            ERROR("Can't write these type of registers (" .. device.name .. ")");
            return 0;
        end
        -- message length
        request[5] = 0;
        request[6] = 6;
        
        -- unit ID
        request[7] = unitId;
        request[8] = device.xtraFields[2];
        
        -- address of register
        request[9] = bit.band(bit.rshift(reg.internalAddr, 8), 255);
        request[10] = bit.band(reg.internalAddr, 255);

        local val = newValue;
        if (reg.dataType == 0) then
            if (val > 0) then
                val = 255*256;
            else
                val = 0;
            end
        end

        -- value of registers
        request[11] = bit.band(bit.rshift(val, 8), 255);
        request[12] = bit.band(val, 255);

        
        local res = sendBytes(request);
  
        if (res == false) then
            DEBUG("Can't send bytes");
            return 0;
        end
        
        local response = {};
        local requestLen = #request;
        
        response = readBytes(requestLen);
        if (response == false) then
          DEBUG("Can't read response");
          return false;
        end

        res = #response;

        if (res ~= requestLen) then
          DEBUG("Wrong response length");
          return false;
        end
        
        for i = 1,res do
            if (response[i] ~= request[i]) then
                DEBUG("Wrong response");
                return false;
            end
        end

    end

    return true;
end

The examples of custom protocols

As an example we've made (partually) several protocols: