custom_protocols
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
custom_protocols [2019/01/09 12:19] – [The examples of custom protocols] emozolyak | custom_protocols [2023/02/21 17:52] (current) – [writeRegister] emozolyak | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | {{ network: | ||
====== Custom protocols ====== | ====== Custom protocols ====== | ||
- | 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 | + | 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 possireble |
===== About Lua ===== | ===== About Lua ===== | ||
Line 21: | Line 22: | ||
To go to the list of user protocols, click on the button " | To go to the list of user protocols, click on the button " | ||
- | {{ :: | + | {{ network: |
You will be taken to the protocol management page. In this example, we see two demonstration protocols - ModBus TCP Demo and ModBus ASCII Demo: | You will be taken to the protocol management page. In this example, we see two demonstration protocols - ModBus TCP Demo and ModBus ASCII Demo: | ||
- | {{ :: | + | {{ network: |
Let's look at the page for editing the ModBus TCP Demo protocol: | Let's look at the page for editing the ModBus TCP Demo protocol: | ||
Line 40: | Line 41: | ||
The regular expression must ensure that the register address is validated on the register editing page (when this protocol is selected). Example: | The regular expression must ensure that the register address is validated on the register editing page (when this protocol is selected). Example: | ||
- | {{ :: | + | {{ network: |
We also see a convenient code editor. It supports formatting, highlighting and validation of syntax. So it's convenient to write the code | 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: | 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: | ||
- | {{:: | + | {{ |
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: | 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: | ||
- | {{ : | + | {{ network: |
===== Necessary functions ===== | ===== Necessary functions ===== | ||
Line 89: | Line 90: | ||
The function readRegister should read the specified register. | 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. | + | In case of successful reading, the function readRegister should return an //(lua table)// **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 |
Three parameters are passed to it as arguments: | Three parameters are passed to it as arguments: | ||
- | reg - Table (structure) with register parameters | + | ***reg** - Table (structure) with register parameters |
- | device - Table (structure) with register type parameters (that were defined in createDevices) | + | ***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. | + | ***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. | + | === Attributes |
- | addr - The original address | + | |
- | 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 | + | *internalAddr - Recalculated register address. This number is recalculated from the specified number system with shift added to it. |
- | base - base value from the corresponding row in createDevices | + | *addr - The original address of the register that the user entered. |
- | xtraFields – xtraFields value from the corresponing row in createDevices | + | *dataType – The type of data that the user specified for the register. 0 = Bit, 1 = Byte, 2 = Word, 3 = Double Word, 4 = UnixTime |
+ | |||
+ | === Attributes of 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. | These parameters are passed in order to be able to correctly and fully compose a request according to the protocol. | ||
- | Для передачи запроса устройству используются функции | + | To send a request to the device //sendBytes// and //sendString// are used. To read the reply //readBytes// and //readString// respectively. Their overview is given in the following table: |
+ | |||
+ | ^ Function ^ Arguments ^ Returns ^ | ||
+ | |sendBytes | ||
+ | |sendString |string| same | | ||
+ | |readBytes | ||
+ | |readString |number (of chars to read)| string | ||
- | На вход sendBytes принимает таблицу | + | To close connection |
- | На вход sendString принимает строку. Результатом будет true в случае успеха и false в случае ошибки. | + | |
- | На вход readBytes принимает количество байт, которое необходимо прочитать. Результатом будет таблица (массив) байт в случае успеха и false в случае ошибки. | + | |
- | На вход readString принимает количество байт, которое необходимо прочитать. Результатом будет строка в случае успеха и false в случае ошибки. | + | |
- | Если необходимо закрыть соединение (например, | + | |
- | Если необходимо сделать паузу, то можно вызвать функцию | + | To make a delay, //sleep// function can be used. Its only argument is time in microseconds, |
- | Для работы с битами можно использовать библиотеку | + | For bit processing refer to bitop library and this [[useful_programs# |
- | Для отладки и вывода диагностических сообщений можно использовать процедуры | + | For debugging and diagnostic messages you can use ERROR, INFO, DEBUG or TRACE from the users Lua scripts - see [[write_to_logs | this]]. |
- | Что бы лучше понять как это все работает давайте рассмотрим пример функции | + | To better understand the custom protocol application, |
<code lua> | <code lua> | ||
Line 259: | Line 264: | ||
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. | 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. | ||
- | Ей передаются все те же параметры, | + | It has the same arguments as the readRegister, |
The writeRegister function may use the same methods of reading from and writing bytes to the port. | The writeRegister function may use the same methods of reading from and writing bytes to the port. | ||
Line 444: | Line 449: | ||
As an example we've made (partually) several protocols: | As an example we've made (partually) several protocols: | ||
- | *ModBus TCP | + | *[[modbus_tcp_custom|ModBus TCP]] |
- | *ModBus ASCII | + | *[[modbus_ascii_custom|ModBus ASCII]] |
- | *[[http:// | + | *[[modbus_rtu_custom|Modbus RTU]]. |
+ | *[[http_get_custom | HTTP request example]] | ||
+ | *Other devices that were connected using [[http:// | ||
- | |||
- | ===== Modbus RTU in custom protocol version ===== | ||
- | |||
- | An example of custom protocol for Modbus RTU. | ||
- | |||
- | *Type: Serial Port | ||
- | *Address validation: ^(C[0-9]+)$|^(DI[0-9]+)$|^(HR[0-9]+)$|^(IR[0-9]+)$ | ||
- | *Validation error message: Invalid register address. Valid ModBus addresses are Cxxx, DIxxx, IRxxx, HRxxx. | ||
- | Code: | ||
- | <code lua> | ||
- | -- MODBUS RTU Demo Driver | ||
- | |||
- | function createDevices () | ||
- | -- read FC write FC | ||
- | addDevice({name = " | ||
- | addDevice({name = " | ||
- | addDevice({name = " | ||
- | addDevice({name = " | ||
- | | ||
- | end | ||
- | |||
- | local errorCount = 0 | ||
- | |||
- | -- template | ||
- | local request = {1, 2, -- slaveId FC | ||
- | | ||
- | | ||
- | | ||
- | } | ||
- | |||
- | EXCEPTIONS = {" | ||
- | " | ||
- | " | ||
- | " | ||
- | " | ||
- | } | ||
- | |||
- | function readRegister (reg, device, unitId) | ||
- | --- FORMING REQUEST ----------- | ||
- | -- slave address | ||
- | request[1] = unitId; | ||
- | | ||
- | -- function code | ||
- | request[2] = device.xtraFields[1] | ||
- | | ||
- | -- address of register | ||
- | request[3] = GetHiByte(reg.internalAddr) | ||
- | request[4] = GetLoByte(reg.internalAddr) | ||
- | | ||
- | -- count of registers | ||
- | local count = 1 | ||
- | if (reg.dataType == 3) then -- double word | ||
- | count = 2 | ||
- | end | ||
- | request[5] = GetHiByte(count) | ||
- | request[6] = GetLoByte(count) | ||
- | | ||
- | -- CRC | ||
- | local crc = GetCRC(request, | ||
- | local crcLo,crcHi = 0,0 -- will be used below too | ||
- | crcLo = GetLoByte(crc) ; request[7] = crcLo | ||
- | crcHi = GetHiByte(crc) ; request[8] = crcHi | ||
- | |||
- | -- SENDING REQUEST | ||
- | if not (sendBytes(request)) then | ||
- | DEBUG(" | ||
- | return false | ||
- | end | ||
- | -- RECEIVING REPLY --- | ||
- | local respHead, respData, respCRC = {}, {}, {} | ||
- | | ||
- | -- read Header with length | ||
- | respHead = readBytes(3) | ||
- | if (respHead == false) then | ||
- | DEBUG(" | ||
- | return false | ||
- | end | ||
- | | ||
- | if (respHead[1] ~= request[1]) then | ||
- | ERROR(" | ||
- | return false | ||
- | end | ||
- | | ||
- | if (respHead[2] ~= request[2]) then | ||
- | if (respHead[2] >= 0x81) then | ||
- | ERROR(" | ||
- | readBytes(2) -- read till the end | ||
- | else | ||
- | ERROR(" | ||
- | end | ||
- | | ||
- | end | ||
- | | ||
- | local resp_Lentgh = respHead[3]; | ||
- | respData = readBytes(resp_Lentgh) | ||
- | if (respData == false) then | ||
- | DEBUG(" | ||
- | return false; | ||
- | end | ||
- | -- check CRC in reply | ||
- | local tmpResponseTab = {} | ||
- | for i,v in ipairs(respHead) do | ||
- | table.insert(tmpResponseTab, | ||
- | end | ||
- | for i,v in ipairs(respData) do | ||
- | table.insert(tmpResponseTab, | ||
- | end | ||
- | | ||
- | crc = GetCRC(tmpResponseTab, | ||
- | crcLo = GetLoByte(crc) | ||
- | crcHi = GetHiByte(crc) | ||
- | | ||
- | respCRC = readBytes(2) | ||
- | if (respCRC == false) then | ||
- | DEBUG(" | ||
- | return false; | ||
- | end | ||
- | if (respCRC[1] ~= crcLo) or (respCRC[2] ~= crcHi) then | ||
- | DEBUG(" | ||
- | return false; | ||
- | end | ||
- | | ||
- | if (device.name == " | ||
- | if (resp_Lentgh ~= count) then | ||
- | ERROR(" | ||
- | return false; | ||
- | end | ||
- | else | ||
- | if (resp_Lentgh ~= count*2) then | ||
- | ERROR(" | ||
- | return false; | ||
- | end | ||
- | end | ||
- | -- RETURN DATA -- | ||
- | return GetHexFromTable(respData) | ||
- | |||
- | end -- readRegister | ||
- | |||
- | |||
- | |||
- | function writeRegister (reg, device, unitId, newValue) | ||
- | -- for read-only don't write | ||
- | DEBUG(" | ||
- | if device.name == " | ||
- | | ||
- | return true | ||
- | end | ||
- | local wrRequest = {}; | ||
- | | ||
- | -- slave address | ||
- | wrRequest[1] = unitId; | ||
- | | ||
- | -- function code | ||
- | wrRequest[2] = device.xtraFields[2] | ||
- | | ||
- | -- address of register | ||
- | wrRequest[3] = GetHiByte(reg.internalAddr) | ||
- | wrRequest[4] = GetLoByte(reg.internalAddr) | ||
- | | ||
- | local dataTable = GetDataAsTable(newValue, | ||
- | | ||
- | local coilsTmp = 0 | ||
- | if (device.name == " | ||
- | coilsTmp = dataTable[2] * 0xFF | ||
- | dataTable[2] = dataTable[1] | ||
- | dataTable[1] = coilsTmp | ||
- | end | ||
- | DEBUG("# | ||
- | | ||
- | wrRequest[5] = dataTable[1] | ||
- | wrRequest[6] = dataTable[2] | ||
- | | ||
- | -- CRC | ||
- | local crc, crcLo, crcHi = 0,0,0 | ||
- | crc = GetCRC(wrRequest, | ||
- | crcLo = GetLoByte(crc) | ||
- | crcHi = GetHiByte(crc) | ||
- | wrRequest[7] = crcLo | ||
- | wrRequest[8] = crcHi | ||
- | | ||
- | DEBUG(" | ||
- | local res = sendBytes(wrRequest); | ||
- | if (res == false) then | ||
- | DEBUG(" | ||
- | return false; | ||
- | end | ||
- | | ||
- | -- читаем ответ | ||
- | res = readBytes(8) | ||
- | | ||
- | if (res == false) then | ||
- | DEBUG(" | ||
- | return false | ||
- | end | ||
- | | ||
- | if (table.concat(res) ~= table.concat(wrRequest)) then | ||
- | DEBUG(" | ||
- | return false | ||
- | end | ||
- | | ||
- | if (#dataTable == 4) then | ||
- | -- DWORD | ||
- | -- repeat steps for 2nd Word | ||
- | wrRequest[3] = GetHiByte(reg.internalAddr + 1) | ||
- | wrRequest[4] = GetLoByte(reg.internalAddr + 1) | ||
- | wrRequest[5] = dataTable[3] | ||
- | wrRequest[6] = dataTable[4] | ||
- | crc, crcLo, crcHi = GetCRC(wrRequest, | ||
- | wrRequest[7] = crcLo | ||
- | wrRequest[8] = crcHi | ||
- | res = sendBytes(wrRequest); | ||
- | if (res == false) then | ||
- | DEBUG(" | ||
- | return false; | ||
- | end | ||
- | end | ||
- | return true | ||
- | end | ||
- | |||
- | |||
- | -- Calculating CRC for RTU | ||
- | function GetCRC(req, offset) | ||
- | | ||
- | local crc = 0xffff | ||
- | local mask = 0 | ||
- | | ||
- | -- iterate bytes | ||
- | for i=1,# | ||
- | crc = bit.bxor(crc, | ||
- | -- iterate bits in byte | ||
- | for j=1,8 do | ||
- | mask = bit.band(crc, | ||
- | if mask == 0x0001 then | ||
- | crc = bit.rshift(crc, | ||
- | crc = bit.bxor(crc, | ||
- | else | ||
- | crc = bit.rshift(crc, | ||
- | end | ||
- | | ||
- | end | ||
- | end | ||
- | | ||
- | end | ||
- | |||
- | function GetHiByte(c) | ||
- | | ||
- | | ||
- | end | ||
- | |||
- | function GetLoByte(input_val) | ||
- | DEBUG(" | ||
- | return bit.band(input_val, | ||
- | end | ||
- | |||
- | function GetHexFromTable(inputTab) | ||
- | -- get hex and concat it to number via string operatoin | ||
- | DEBUG(" | ||
- | local numberAs_String = "" | ||
- | local tmpStr = "" | ||
- | | ||
- | for i,v in pairs(inputTab) do | ||
- | tmpStr = string.format(" | ||
- | if (#tmpStr == 1) then | ||
- | tmpStr = " | ||
- | end | ||
- | numberAs_String = numberAs_String..tmpStr | ||
- | end | ||
- | DEBUG(" | ||
- | return tonumber(numberAs_String, | ||
- | end | ||
- | |||
- | function GetDataAsTable(value, | ||
- | |||
- | local highWord, lowWord, tmpTable = 0, 0, {} | ||
- | | ||
- | if (datatype ~= 3) then | ||
- | DEBUG(" | ||
- | tmpTable[1] = GetHiByte(value) ; DEBUG(tmpTable[1]) | ||
- | tmpTable[2] = GetLoByte(value) ; DEBUG(tmpTable[2]) | ||
- | else | ||
- | highWord = bit.rshift(value, | ||
- | lowWord = bit.band(c, | ||
- | | ||
- | tmpTable[1] = GetHiByte(highWord) | ||
- | tmpTable[2] = GetLoByte(highWord) | ||
- | tmpTable[3] = GetHiByte(lowWord) | ||
- | tmpTable[4] = GetLoByte(lowWord) | ||
- | end | ||
- | return tmpTable | ||
- | end | ||
- | </ | ||
custom_protocols.1547036362.txt.gz · Last modified: 2019/01/09 12:19 by emozolyak