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 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 = "IR", shift = 0, base = 10, xtraFields = {4, 0}})
 
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 
                }     
 
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)
 
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 
    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, device.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]
        crc, crcLo, crcHi = GetCRC(wrRequest, 0), GetLoByte(crc) , GetHiByte(crc) 
        wrRequest[7] = crcLo
        wrRequest[8] = crcHi 
        res = sendBytes(wrRequest);
        if (res == false) then
            DEBUG("Can't send request");
            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,#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(c,0xFFFF)
 
        tmpTable[1] = GetHiByte(highWord)
        tmpTable[2] = GetLoByte(highWord)
        tmpTable[3] = GetHiByte(lowWord)
        tmpTable[4] = GetLoByte(lowWord)
    end 
    return tmpTable
end
modbus_rtu_custom.txt · Last modified: 2019/01/09 12:18 by emozolyak