User Tools

Site Tools


modbus_rtu_custom
no way to compare when less than two revisions

Differences

This shows you the differences between two versions of the page.


Previous revision
Last revision
modbus_rtu_custom [2022/10/21 12:01] – Float mode for 32 bit (DW) read and write atolstov
Line 1: Line 1:
 +
 +
 +===== 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 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
 +
 +</code>
 +
  
modbus_rtu_custom.txt · Last modified: 2023/11/30 10:01 by emozolyak

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki