User Tools

Site Tools


modbus_rtu_custom

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:

ModBus RTU custom protocol.lua
-- MODBUS RTU Demo Driver
 
function createDevices ()
                                                     -- read FC write FC
    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}})
    addDevice({name = "HRI",    shift = 0, base = 10, xtraFields = {3, 6, 1, 0}})   -- uint 0 inverse 
    addDevice({name = "HRIF",   shift = 0, base = 10, xtraFields = {3, 6, 1, 5}})   -- float inverse (maybe 7 ? https://docs.webhmi.com.ua/access_via_api?s[]=float)
    addDevice({name = "HRF",    shift = 0, base = 10, xtraFields = {3, 6, 0, 5}})   -- float 5
    addDevice({name = "IR",     shift = 0, base = 10, xtraFields = {4, 0}})
 
end
 
local errorCount = 0
 
SLAVE_ADDR    = 1
FUNC_CODE     = 2
REG_ADDR_HI   = 3
REG_ADDR_LO   = 4
DATA_LEN_HI   = 5;
DATA_LEN_LO   = 6
CRC_POS_LO    = 7 -- CRC LITTLE-ENDIAN (lowest comes first!)
CRC_POS_HI    = 8
 
CRC_BIG_ENDIAN      = false
if CRC_BIG_ENDIAN then
    CRC_POS_HI      = 7
    CRC_POS_LO      = 8 -- CRC BIG-ENDIAN (biggest comes first)
end
-- template 
local request = {SLAVE_ADDR, FUNC_CODE,     
                 REG_ADDR_HI,  REG_ADDR_LO,   
                 DATA_LEN_HI,  DATA_LEN_LO,   
                 CRC_POS_LO,  CRC_POS_HI
                }     
 
EXCEPTIONS = {  "Illegal Function",         "Illegal Data Address",
                "Illegal Data Value",       "Slave Device Failure",
                "Acknowledge",              "Slave Device Busy",
                "Negative Acknowledge",     "Memory Parity Error",
                "Gateway Path Unavailable", "Gateway Target Device Failed to Respond"
              }
 
-- dataType – The type of data that the user specified for the register. 0 = Bit, 1 = Byte, 2 = Word, 3 = Double Word, 4 = UnixTime
DATATYPE    = {DW = 3}
DATALEN     = {DW = 4, WORD=2, BYTE=1,BIT=1}
FORMAT      = {UINT = 0, FLOAT_32=5}
 
table.unpack = unpack
function readRegister (reg, device, unitId)
--------------------------------------------- FORMING REQUEST ----------------------------------------------
  request[SLAVE_ADDR] = unitId
 
  request[FUNC_CODE] = device.xtraFields[1]
 
 
  request[REG_ADDR_HI] = GetHiByte(reg.internalAddr)
  request[REG_ADDR_LO] = GetLoByte(reg.internalAddr)
 
  count = reg.dataType == DATATYPE.DW and 2 or 1
 
  request[DATA_LEN_HI] = GetHiByte(count)
  request[DATA_LEN_LO] = GetLoByte(count)
 
 
  local crc = GetCRC(request, 2)
  local crcLo, crcHi -- will be used below too
  crcLo = GetLoByte(crc) ; request[CRC_POS_LO] = crcLo
  crcHi = GetHiByte(crc) ; request[CRC_POS_HI] = crcHi
 
---------------------------------------------- SENDING REQUEST ----------------------------------------------
  if not (sendBytes(request)) then
      DEBUG("Can't send bytes")
      return false
  end
 
---------------------------------------------- RECEIVING REPLY ----------------------------------------------
  local respHead, respData, respCRC = {}, {}, {}
 
    -- read Header with length
  respHead = readBytes(3)
  if (respHead == false) then
      DEBUG("Can't read response")
      return false
  end
 
  if (respHead[SLAVE_ADDR] ~= request[SLAVE_ADDR]) then
      ERROR("Wrong slaveID in response!")
      return false
  end
 
    if (respHead[FUNC_CODE] ~= request[FUNC_CODE]) then
        if (respHead[FUNC_CODE] >= 0x81) then
          ERROR("EXCEPTION: "..EXCEPTIONS[bit.band(respHead[FUNC_CODE], 0x0F)])
          readBytes(2) -- read till the end
        else
          ERROR("Wrong Func Code in response. In response = 0d" .. tostring( respHead[FUNC_CODE]) .. ', but expected = 0d' .. tostring(request[FUNC_CODE]))
        end
        return false;
    end
 
    local resp_Lentgh = respHead[3];
        respData = readBytes(resp_Lentgh)
    if (respData == false) then
      DEBUG("Can't read response");
      return false;
    end
    local tmpResponseTab = {}
    for i,v in ipairs(respHead) do
        table.insert(tmpResponseTab, v)
    end
    for i,v in ipairs(respData) do
        table.insert(tmpResponseTab, v)
    end
 
    -- check CRC in reply
    crc = GetCRC(tmpResponseTab, 0)
    crcLo = GetLoByte(crc)
    crcHi = GetHiByte(crc)
 
    respCRC = readBytes(2)
    if (respCRC == false) then
        DEBUG("Can't read response");
        return false;
    end
    if (respCRC[1] ~= crcLo) or (respCRC[2] ~= crcHi) then
        DEBUG("Wrong CRC in reply! "..string.format("%X", crcLo).." "..string.format("%X", crcHi));
        return false;
    end
 
    if (device.name == "DI") or (device.name == "C")  then
      if (resp_Lentgh ~= count) then
          ERROR("Wrong length in response");
          return false;
      end
    else
        if (resp_Lentgh ~= count*2) then
          ERROR("Wrong length in response");
          return false;
        end
    end
---------------------------------------------- RETURN DATA ----------------------------------------------
 
    local inversion = device.xtraFields[3]
    return GetHexFromTable(respData, not((inversion or 0) == 1))
 
end -- readRegister
 
function GetHexFromTable(inputTab, _invert)
    if #inputTab > 2 then
    local invert = _invert or false
        if invert then
            invertedInputTab = {}
            half1, half2 = SplitInHalf(inputTab)
 
            for i, v in pairs(half2) do table.insert(invertedInputTab, v) end
            for i, v in pairs(half1) do table.insert(invertedInputTab, v) end
            inputTab = invertedInputTab;
        end
 
        local toReturn = {}
        for i,v in pairs(inputTab) do toReturn[i]=tonumber(string.format("%X",v),16) end
 
        return toReturn
    else
        -- get hex and concat it to number via string operatoin
                                        -- TRACE("entered GetHexFromTable with table - "..table.concat(inputTab))
        local numberAs_String = ""
        local tmpStr = ""
 
            for i,v in pairs(inputTab) do
                tmpStr = string.format("%X",v)
                if (#tmpStr == 1) then
                    tmpStr = "0"..tmpStr
                end
                numberAs_String = numberAs_String..tmpStr
            end
                                        -- TRACE("GetHexFromTable: decimal number is "..table.concat(inputTab).." hexadecimal = ".. numberAs_String)
        return tonumber(numberAs_String, 16)
    end
end
 
function writeRegister (reg, device, unitId, newValue)
    local inversion = not(device.xtraFields[3] == 1 or false)
 
    reg.value_format = device.xtraFields[4]
    if reg.dataType == DATATYPE.DW and reg.value_format == FORMAT.FLOAT_32 then
        DEBUG("Going to write this value ".. (decodeIEEE754FloatToLua(newValue) or 'nil') .. ', \nuint(dec): ' .. '0d'..newValue .. ', binary: '  .. '0b'..table.concat(toBits(newValue,32)) .. ', uint(hex): ' .. string.format("0x%x", newValue)) -- TRACE("newValue = 0d" .. newValue .. ' hex = 0x' .. string.format("%X", newValue))
    else
        DEBUG("Going to write this value "..newValue)
    end
 
    if device.name == "IR" or device.name == "DI" then
         ERROR("Can't write these type of registers (" .. device.name .. ")")
        return true
    end
 
    local wrRequest = {};
    wrRequest[SLAVE_ADDR] = unitId;
 
    wrRequest[FUNC_CODE] = device.xtraFields[2]
 
    wrRequest[REG_ADDR_HI] = GetHiByte(reg.internalAddr)
    wrRequest[REG_ADDR_LO] = GetLoByte(reg.internalAddr)
 
    local dataTable = GetDataAsTable(newValue, reg.dataType)
    if reg.dataType == DATATYPE.DW and inversion then
        dataTableTmp = {}
        dataTableTmp[1]=dataTable[3]; dataTableTmp[2]=dataTable[4]
        dataTableTmp[3]=dataTable[1]; dataTableTmp[4]=dataTable[2]
        dataTable=dataTableTmp
    end
 
    -- local coilsTmp = 0
    if (device.name == "C") then
        local coilsTmp = dataTable[2] * 0xFF
        dataTable[2] = dataTable[1]
        dataTable[1] = coilsTmp
    end
    wrRequest[DATA_LEN_HI] = dataTable[1]
    wrRequest[DATA_LEN_LO] = dataTable[2]
 
    local crc, crcLo, crcHi = 0,0,0
    crc = GetCRC(wrRequest, 0)
    crcLo = GetLoByte(crc)
    crcHi = GetHiByte(crc)
    wrRequest[CRC_POS_LO] = crcLo
    wrRequest[CRC_POS_HI] = crcHi
 
    local res = sendBytes(wrRequest);
    if (res == false) then
        DEBUG("Can't send request");
        return false;
    end
 
    -- read response
    res = readBytes(8) 
 
    if (res == false) then
        DEBUG("Can't receive reply")
        return false
    end 
 
    if (table.concat(res) ~= table.concat(wrRequest)) then 
        DEBUG("Response does not match!")
        return false
    end 
 
    if (#dataTable == DATALEN.DW) then 
        -- TRACE("DWORD CASE!!!")
        -- repeat steps for 2nd Word 
        wrRequest[REG_ADDR_HI] = GetHiByte(reg.internalAddr + 1)
        wrRequest[REG_ADDR_LO] = GetLoByte(reg.internalAddr + 1)
        wrRequest[DATA_LEN_HI] = dataTable[3]
        wrRequest[DATA_LEN_LO] = dataTable[4]
 
            crc = GetCRC(wrRequest, 2); crcLo = GetLoByte(crc); crcHi = GetHiByte(crc)  -- offset = 2 for omit crc from previous word 
 
            wrRequest[CRC_POS_LO] = crcLo
            wrRequest[CRC_POS_HI] = crcHi 
 
        res = sendBytes(wrRequest);
        if (res == false) then
            DEBUG("Can't send request");
            return false;
        end
    end 
    return true
end
 
 
-- Calculating CRC16 for MODBUS RTU 
function GetCRC(req, offset) 
 
  local crc = 0xffff
  local mask = 0
 
  -- iterate bytes
for i=1,#req-offset do
      crc = bit.bxor(crc,req[i])
      -- iterate bits in byte 
      for j=1,8 do
          mask = bit.band(crc,0x0001)
          if mask == 0x0001 then
              crc = bit.rshift(crc,1)
              crc = bit.bxor(crc,0xA001)
          else
              crc = bit.rshift(crc,1)
          end
 
      end 
end 
     return crc
end
 
function GetHiByte(c)
        assert(c < 65536, "This is not a two bytes!")
                                    -- DEBUG("Going to get high byte from "..c .. '      0x'.. string.format("%04X", c))
   return bit.rshift(c,8)
end
 
function GetLoByte(c)
        assert(c < 65536, "This is not a two bytes!")
                                    -- DEBUG("Going to get  low byte from "..c .. '      0x'.. string.format("%04X", c))
    return bit.band(c,0xFF)
end
 
function SplitInHalf(full)
    local h1, h2 = {}, {}
    local half = math.ceil(#full/2)
    for i = 1, half do
        table.insert(h1, full[i])
    end
    for i = half+1, #full do
        table.insert(h2, full[i])
    end
    return h1,h2
end
 
function GetDataAsTable(value, datatype)
 
    local highWord, lowWord, tmpTable = 0, 0, {}
 
    if (datatype ~= DATATYPE.DW) then 
        DEBUG("GetDataAsTable - going to process value  "..value)
        tmpTable[1] = GetHiByte(value) ; DEBUG('tmpTable[1] = ' .. tmpTable[1])
        tmpTable[2] = GetLoByte(value) ; DEBUG('tmpTable[2] = ' .. tmpTable[2])
    else 
        highWord    = bit.rshift(value,     16)
        lowWord     = bit.band  (value,     0xFFFF)
 
        tmpTable[1] = GetHiByte(highWord)
        tmpTable[2] = GetLoByte(highWord)
        tmpTable[3] = GetHiByte(lowWord)
        tmpTable[4] = GetLoByte(lowWord)
 
        DEBUG("GetDataAsTable DW = - " .. table.concat(tmpTable))
    end 
    return tmpTable
end
 
function toBits(num,bits)
	-- returns a table of bits, most significant first.
	bits = bits or math.max(1, select(2, math.frexp(num)))
	local t = {} -- will contain the bits
	for b = bits, 1, -1 do
		t[b] = math.fmod(num, 2)
		num = math.floor((num - t[b]) / 2)
	end
	return t
end
 
 
function Bin2Hex(s)
    assert (type(s) == "string", "binary as string expected")
    -- s 	-> binary string
    local bin2hex = {
    	["0000"] = "0", ["0001"] = "1", ["0010"] = "2", ["0011"] = "3",
    	["0100"] = "4", ["0101"] = "5", ["0110"] = "6", ["0111"] = "7",
    	["1000"] = "8", ["1001"] = "9", ["1010"] = "A", ["1011"] = "B",
    	["1100"] = "C", ["1101"] = "D", ["1110"] = "E", ["1111"] = "F"
	}
 
    tabBytes={}
 
    local l = 0
    local h, b = "", ""
    local rem
 
    l = string.len(s)
    rem = l % 4
    l = l-1
 
	-- need to prepend zeros to eliminate mod 4
	if (rem > 0) then
		s = string.rep("0", 4 - rem)..s
	end
 
	for i = 1, l, 4 do
		b = string.sub(s, i, i+3)
		table.insert(tabBytes,bin2hex[b])
		if not b then ERROR("bin2hex b is nil") end
		TRACE("bin2hex b = " .. tostring(b))
		h = h..bin2hex[b]
	end
 
	return h, tabBytes
end
 
 
function hex2dec(hexstr)
	return tonumber(hexstr,16)
end
 
function decodeIEEE754FloatToLua(input)
    sign = input < 0 and -1 or 1 
    input = math.abs(input)
 
	bitsTable = toBits(input,32)
 
	exponentTable={table.unpack(bitsTable, 2,9)} 
	mantissaTable={table.unpack(bitsTable,10)} 	
 
	mantissaStr, manstissaTabBytes = Bin2Hex(table.concat(mantissaTable))
	mantissaNum = hex2dec(table.concat(manstissaTabBytes))  
 
 
	exponentStr, exponentBytesTable = Bin2Hex(table.concat(exponentTable)) 
	exponentNum =  hex2dec(table.concat(exponentBytesTable)) 
 
	exponent = exponentNum - 127 -- 0 - 127 = -127 --> denormalized mode
	mantissa=mantissaNum/8388608
 
	if exponent == -127 then -- denormalized mode
		exponent = -126
		mantissa = mantissa
	else
		mantissa = mantissa + 1
	end
 
	float_number=math.ldexp(mantissa,exponent) * sign
	return float_number
end

Version 2

-- MODBUS RTU Driver
 
function createDevices ()
                                                     -- read FC write FC
  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}})
  addDevice({name = "IR", shift = 0, base = 10, xtraFields = {4, 0}})
  addDevice({name = "MHR",shift = 0, base = 10, xtraFields = {3, 16}})
 
end
 
local errorCount = 0
 
-- template 
local request = {1, 2,      -- slaveId FC
                 3,  4,     -- addr high lo 
                 5,  6,     -- count hi lo 
                 0,  0      -- crc high lo 
                }
 
local requestM = {1, 2,     -- Адрес устройства, Функциональный код
                 3,  4,     -- Адрес первого регистра Hi, Lo
                 5,  6,     -- Количество регистров Hi, Lo
                 7,         -- Количество байт далее
                 8,  9,     -- Значение1 Hi, Lo
                 10, 11,    -- Значение2 Hi, Lo
                 -------------------------------
                 0,  0      -- Контрольная сумма CRC Hi, Lo
                }  
 
EXCEPTIONS = {"Illegal Function",     "Illegal Data Address",
               "Illegal Data Value",  "Slave Device Failure",
               "Acknowledge",         "Slave Device Busy",
               "Negative Acknowledge", "Memory Parity Error",
               "Gateway Path Unavailable", "Gateway Target Device Failed to Respond"
              }
 
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, 2)
  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("Can't send bytes")
      return false
  end
                           -- RECEIVING REPLY --- 
  local respHead, respData, respCRC = {}, {}, {}
 
    -- read Header with length 
  respHead = readBytes(3)
  if (respHead == false) then
      DEBUG("Can't read response")
      return false
  end
 
  if (respHead[1] ~= request[1]) then
      ERROR("Wrong slaveID in response!")
      return false
  end
 
  if (respHead[2] ~= request[2]) then
      if (respHead[2] >= 0x81) then 
          ERROR("EXCEPTION: "..EXCEPTIONS[bit.band(respHead[2], 0x0F)])
          readBytes(2) -- read till the end 
      else       
          ERROR("Wrong Func Code in response")
      end 
   return false;
  end
 
  local resp_Lentgh = respHead[3]; 
        respData = readBytes(resp_Lentgh)
  if (respData == false) then
      DEBUG("Can't read response");
      return false;
  end
  -- check CRC in reply 
  local tmpResponseTab = {}
    for i,v in ipairs(respHead) do 
        table.insert(tmpResponseTab, v) 
    end 
    for i,v in ipairs(respData) do 
        table.insert(tmpResponseTab, v) 
    end 
 
  crc = GetCRC(tmpResponseTab, 0)
  crcLo = GetLoByte(crc)
  crcHi = GetHiByte(crc)
 
  respCRC = readBytes(2)
  if (respCRC == false) then
      DEBUG("Can't read response");
      return false;
  end
  if (respCRC[1] ~= crcLo) or (respCRC[2] ~= crcHi) then 
      DEBUG("Wrong CRC in reply! "..string.format("%X", crcLo).." "..string.format("%X", crcHi));
      return false;
  end 
 
  if (device.name == "DI") or (device.name == "C")  then 
      if (resp_Lentgh ~= count) then
          ERROR("Wrong length in response");
          return false;
      end
  else 
      if (resp_Lentgh ~= count*2) then
          ERROR("Wrong length in response");
          return false;
      end
  end 
                                  -- RETURN DATA --  
    --return GetHexFromTable(respData)
    return respData
 
end -- readRegister
 
 
 
function writeRegister (reg, device, unitId, newValue)
    -- for read-only don't write 
                                    DEBUG("Going to write this value "..newValue)
    if device.name == "IR" or device.name == "DI" then 
         ERROR("Can't write these type of registers (" .. device.name .. ")")
        return true 
    end 
    if device.name == "MHR" then 
         ERROR("Can't write these type of registers (" .. device.name .. ")")
 
        -------------------------------------- My Write MHR --------------------------------------
        local wrRequest = {};
        local n_byte =1;
 
        -- Адрес устройства
        wrRequest[n_byte] = unitId;   
 
        -- Функциональный код
        n_byte = n_byte +1;
        wrRequest[n_byte] = device.xtraFields[2]
 
        -- Адрес первого регистра Hi, Lo
        n_byte = n_byte +1;
        wrRequest[n_byte] = GetHiByte(reg.internalAddr)
        n_byte = n_byte +1;
        wrRequest[n_byte] = GetLoByte(reg.internalAddr)
 
        -- копируем в dataTable байты которые нужно записать
        local dataTable = GetDataAsTable(newValue, reg.dataType)
 
        local kol = #dataTable; -- количество байт которые нужно записать
        -- Количество регистров Hi, Lo
        n_byte = n_byte +1;
        wrRequest[n_byte] = 0;
        n_byte = n_byte +1;
        wrRequest[n_byte] = kol/2;
 
        -- Количество байт далее
        n_byte = n_byte +1;
        wrRequest[n_byte] = kol;
 
        -- Значение 1,2,3... Hi, Lo
        for i = 1, kol do 
            n_byte = n_byte +1;
            wrRequest[n_byte] = dataTable[i]
        end
 
        -- CRC
        local crc, crcLo, crcHi = 0,0,0
        crc = GetCRC(wrRequest, 0)
        crcLo = GetLoByte(crc)
        crcHi = GetHiByte(crc) 
        n_byte = n_byte +1;
        wrRequest[n_byte] = crcLo
        n_byte = n_byte +1;
        wrRequest[n_byte] = crcHi 
 
 
        DEBUG("Going to send this packet "..table.concat(wrRequest))    
        local res = sendBytes(wrRequest);
        if (res == false) then
            DEBUG("Can't send request");
            return false;
        end
 
        -- читаем ответ 
        res = readBytes(8) 
 
        if (res == false) then
            DEBUG("Can't receive reply")
            return false
        end 
 
        -- проверяем записалось или нет по количеству записанных регистров
        if ( res[6] ~= (kol/2)) then 
            DEBUG("Response does not match!")
            return false
        end
        -------------------------------------- My Write MHR --------------------------------------
 
        return true 
    else 
 
        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, reg.dataType)
 
            local coilsTmp = 0 
            if (device.name == "C") then 
                coilsTmp = dataTable[2] * 0xFF 
                dataTable[2] = dataTable[1]
                dataTable[1] = coilsTmp
            end 
        DEBUG("#dataTable now "..#dataTable)
 
        wrRequest[5] = dataTable[1]
        wrRequest[6] = dataTable[2]
 
        -- CRC
        local crc, crcLo, crcHi = 0,0,0
        crc = GetCRC(wrRequest, 0)
        crcLo = GetLoByte(crc)
        crcHi = GetHiByte(crc) 
        wrRequest[7] = crcLo
        wrRequest[8] = crcHi 
 
        DEBUG("Going to send this packet "..table.concat(wrRequest))    
        local res = sendBytes(wrRequest);
        if (res == false) then
            DEBUG("Can't send request");
            return false;
        end
 
        -- читаем ответ 
        res = readBytes(8) 
 
        if (res == false) then
            DEBUG("Can't receive reply")
            return false
        end 
 
        if (table.concat(res) ~= table.concat(wrRequest)) then 
            DEBUG("Response does not match!")
            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]
 
            wrRequest[7] = nil -- удаляем ячейку со старым CRC
            wrRequest[8] = nil -- удаляем ячейку со старым CRC
 
            -- CRC
            crc = GetCRC(wrRequest, 0)
            crcLo = GetLoByte(crc)
            crcHi = GetHiByte(crc) 
            wrRequest[7] = crcLo
            wrRequest[8] = crcHi 
 
            DEBUG("Going to send this packet "..table.concat(wrRequest)) 
            res = sendBytes(wrRequest);
            if (res == false) then
                DEBUG("Can't send request");
                return false;
            end
 
            -- читаем ответ 
            res = readBytes(8) 
 
            if (res == false) then
                DEBUG("Can't receive reply")
                return false
            end 
        end 
        return true
    end
end
 
 
-- Calculating CRC for RTU 
function GetCRC(req, offset) 
 
  local crc = 0xffff
  local mask = 0
 
  -- iterate bytes
for i=1,#req-offset do
      crc = bit.bxor(crc,req[i])
      -- iterate bits in byte 
      for j=1,8 do
          mask = bit.band(crc,0x0001)
          if mask == 0x0001 then
              crc = bit.rshift(crc,1)
              crc = bit.bxor(crc,0xA001)
          else
              crc = bit.rshift(crc,1)
          end
 
      end 
end 
     return crc
end
 
function GetHiByte(c)
                                   DEBUG("Going to get high byte from "..c)
   return bit.rshift(c,8)
end
 
function GetLoByte(input_val) 
                                    DEBUG("Going to get low byte from "..input_val)
    return bit.band(input_val,0xFF)
end
 
function GetHexFromTable(inputTab)
    -- get hex and concat it to number via string operatoin 
                                    DEBUG("entered GetHexFromTable with table - "..table.concat(inputTab))
    local numberAs_String = "" 
    local tmpStr = "" 
 
        for i,v in pairs(inputTab) do 
            tmpStr = string.format("%X",v)
            if (#tmpStr == 1) then 
                tmpStr = "0"..tmpStr
            end 
            numberAs_String = numberAs_String..tmpStr
        end 
                                DEBUG("number is "..numberAs_String.." decimal = "..numberAs_String)    
    return tonumber(numberAs_String, 16)
end
 
function GetDataAsTable(value, datatype)
 
    local highWord, lowWord, tmpTable = 0, 0, {}
 
    if (datatype ~= 3) then 
        DEBUG("getdataastable - going to process value  "..value)
        tmpTable[1] = GetHiByte(value) ; DEBUG(tmpTable[1])
        tmpTable[2] = GetLoByte(value) ; DEBUG(tmpTable[2])
    else 
        highWord = bit.rshift(value,16)
        lowWord = bit.band(value,0xFFFF)
 
        tmpTable[1] = GetHiByte(highWord)
        tmpTable[2] = GetLoByte(highWord)
        tmpTable[3] = GetHiByte(lowWord)
        tmpTable[4] = GetLoByte(lowWord)
    end 
    return tmpTable
end