modbus_rtu_custom
Differences
This shows you the differences between two versions of the page.
Last revisionBoth sides next revision | |||
modbus_rtu_custom [2019/01/09 12:18] – created emozolyak | modbus_rtu_custom [2022/10/21 12:01] – Float mode for 32 bit (DW) read and write atolstov | ||
---|---|---|---|
Line 9: | Line 9: | ||
*Validation error message: Invalid register address. Valid ModBus addresses are Cxxx, DIxxx, IRxxx, HRxxx. | *Validation error message: Invalid register address. Valid ModBus addresses are Cxxx, DIxxx, IRxxx, HRxxx. | ||
Code: | Code: | ||
- | <code lua> | + | < |
-- MODBUS RTU Demo Driver | -- MODBUS RTU Demo Driver | ||
function createDevices () | function createDevices () | ||
-- read FC write FC | -- read FC write FC | ||
- | | + | |
- | addDevice({name = " | + | addDevice({name = " |
- | addDevice({name = " | + | addDevice({name = " |
- | addDevice({name = " | + | |
- | + | addDevice({name = " | |
+ | addDevice({name = " | ||
+ | | ||
+ | |||
end | end | ||
+ | |||
local errorCount = 0 | local errorCount = 0 | ||
+ | |||
+ | SLAVE_ADDR | ||
+ | FUNC_CODE | ||
+ | REG_ADDR_HI | ||
+ | REG_ADDR_LO | ||
+ | DATA_LEN_HI | ||
+ | DATA_LEN_LO | ||
+ | CRC_POS_LO | ||
+ | CRC_POS_HI | ||
+ | CRC_BIG_ENDIAN | ||
+ | if CRC_BIG_ENDIAN then | ||
+ | CRC_POS_HI | ||
+ | CRC_POS_LO | ||
+ | end | ||
-- template | -- template | ||
- | local request = {1, 2, -- slaveId FC | + | local request = {SLAVE_ADDR, FUNC_CODE, |
- | 3, | + | REG_ADDR_HI, |
- | 5, | + | DATA_LEN_HI, |
- | 0, | + | CRC_POS_LO, |
} | } | ||
- | + | ||
- | EXCEPTIONS = {" | + | EXCEPTIONS = { " |
- | | + | " |
- | | + | " |
- | | + | " |
- | | + | " |
} | } | ||
+ | -- dataType – The type of data that the user specified for the register. 0 = Bit, 1 = Byte, 2 = Word, 3 = Double Word, 4 = UnixTime | ||
+ | DATATYPE | ||
+ | DATALEN | ||
+ | FORMAT | ||
+ | |||
+ | table.unpack = unpack | ||
function readRegister (reg, device, unitId) | function readRegister (reg, device, unitId) | ||
- | --- FORMING REQUEST ----------- | + | --------------------------------------------- FORMING REQUEST ---------------------------------------------- |
- | | + | request[SLAVE_ADDR] = unitId |
- | request[1] = 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) |
- | + | ||
- | -- 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 crc = GetCRC(request, | ||
- | local crcLo, | + | local crcLo, crcHi -- will be used below too |
- | crcLo = GetLoByte(crc) ; request[7] = crcLo | + | crcLo = GetLoByte(crc) ; request[CRC_POS_LO] = crcLo |
- | crcHi = GetHiByte(crc) ; request[8] = crcHi | + | crcHi = GetHiByte(crc) ; request[CRC_POS_HI] = crcHi |
- | -- SENDING REQUEST | + | ---------------------------------------------- SENDING REQUEST |
if not (sendBytes(request)) then | if not (sendBytes(request)) then | ||
DEBUG(" | DEBUG(" | ||
return false | return false | ||
end | end | ||
- | -- RECEIVING REPLY --- | + | |
+ | ---------------------------------------------- RECEIVING REPLY ---------------------------------------------- | ||
local respHead, respData, respCRC = {}, {}, {} | local respHead, respData, respCRC = {}, {}, {} | ||
- | | + | |
- | -- read Header with length | + | -- read Header with length |
respHead = readBytes(3) | respHead = readBytes(3) | ||
if (respHead == false) then | if (respHead == false) then | ||
Line 77: | Line 96: | ||
return false | return false | ||
end | end | ||
- | | + | |
- | if (respHead[1] ~= request[1]) then | + | if (respHead[SLAVE_ADDR] ~= request[SLAVE_ADDR]) then |
ERROR(" | ERROR(" | ||
return false | return false | ||
end | end | ||
- | | + | |
- | if (respHead[2] ~= request[2]) then | + | if (respHead[FUNC_CODE] ~= request[FUNC_CODE]) then |
- | if (respHead[2] >= 0x81) then | + | if (respHead[FUNC_CODE] >= 0x81) then |
- | ERROR(" | + | ERROR(" |
- | readBytes(2) -- read till the end | + | readBytes(2) -- read till the end |
- | else | + | else |
- | ERROR(" | + | ERROR(" |
- | end | + | end |
- | | + | return false; |
- | end | + | end |
- | + | ||
- | local resp_Lentgh = respHead[3]; | + | local resp_Lentgh = respHead[3]; |
respData = readBytes(resp_Lentgh) | respData = readBytes(resp_Lentgh) | ||
- | | + | |
DEBUG(" | DEBUG(" | ||
return false; | return false; | ||
- | | + | |
- | -- check CRC in reply | + | local tmpResponseTab = {} |
- | | + | for i,v in ipairs(respHead) do |
- | for i,v in ipairs(respHead) do | + | table.insert(tmpResponseTab, |
- | table.insert(tmpResponseTab, | + | end |
- | end | + | for i,v in ipairs(respData) do |
- | for i,v in ipairs(respData) do | + | table.insert(tmpResponseTab, |
- | table.insert(tmpResponseTab, | + | end |
- | end | + | |
- | + | -- check CRC in reply | |
- | crc = GetCRC(tmpResponseTab, | + | |
- | crcLo = GetLoByte(crc) | + | crcLo = GetLoByte(crc) |
- | crcHi = GetHiByte(crc) | + | crcHi = GetHiByte(crc) |
- | + | ||
- | respCRC = readBytes(2) | + | respCRC = readBytes(2) |
- | if (respCRC == false) then | + | if (respCRC == false) then |
- | DEBUG(" | + | DEBUG(" |
- | return false; | + | return false; |
- | end | + | end |
- | if (respCRC[1] ~= crcLo) or (respCRC[2] ~= crcHi) then | + | if (respCRC[1] ~= crcLo) or (respCRC[2] ~= crcHi) then |
- | DEBUG(" | + | DEBUG(" |
- | return false; | + | return false; |
- | end | + | end |
- | + | ||
- | if (device.name == " | + | if (device.name == " |
if (resp_Lentgh ~= count) then | if (resp_Lentgh ~= count) then | ||
ERROR(" | ERROR(" | ||
return false; | return false; | ||
end | end | ||
- | | + | |
- | if (resp_Lentgh ~= count*2) then | + | if (resp_Lentgh ~= count*2) then |
ERROR(" | ERROR(" | ||
return false; | return false; | ||
- | | + | |
- | end | + | end |
- | -- RETURN DATA -- | + | ---------------------------------------------- RETURN DATA ---------------------------------------------- |
- | return GetHexFromTable(respData) | + | |
+ | local inversion = device.xtraFields[3] | ||
+ | return GetHexFromTable(respData, not((inversion or 0) == 1)) | ||
end -- readRegister | end -- readRegister | ||
+ | function GetHexFromTable(inputTab, | ||
+ | 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, | ||
+ | for i, v in pairs(half1) do table.insert(invertedInputTab, | ||
+ | inputTab = invertedInputTab; | ||
+ | end | ||
+ | local toReturn = {} | ||
+ | for i,v in pairs(inputTab) do toReturn[i]=tonumber(string.format(" | ||
+ | |||
+ | return toReturn | ||
+ | else | ||
+ | -- get hex and concat it to number via string operatoin | ||
+ | -- TRACE(" | ||
+ | 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 | ||
+ | -- TRACE(" | ||
+ | return tonumber(numberAs_String, | ||
+ | end | ||
+ | end | ||
+ | |||
function writeRegister (reg, device, unitId, newValue) | function writeRegister (reg, device, unitId, newValue) | ||
- | -- for read-only don't write | + | |
- | DEBUG(" | + | |
- | if device.name == " | + | reg.value_format = device.xtraFields[4] |
+ | if reg.dataType == DATATYPE.DW and reg.value_format == FORMAT.FLOAT_32 then | ||
+ | DEBUG(" | ||
+ | else | ||
+ | DEBUG(" | ||
+ | end | ||
+ | |||
+ | if device.name == " | ||
| | ||
- | return true | + | return true |
- | end | + | end |
- | local wrRequest = {}; | + | |
| | ||
- | | + | |
- | wrRequest[1] = unitId; | + | wrRequest[SLAVE_ADDR] = unitId; |
- | + | ||
- | -- function code | + | wrRequest[FUNC_CODE] = device.xtraFields[2] |
- | wrRequest[2] = device.xtraFields[2] | + | |
- | + | wrRequest[REG_ADDR_HI] = GetHiByte(reg.internalAddr) | |
- | -- address of register | + | wrRequest[REG_ADDR_LO] = GetLoByte(reg.internalAddr) |
- | wrRequest[3] = GetHiByte(reg.internalAddr) | + | |
- | wrRequest[4] = GetLoByte(reg.internalAddr) | + | local dataTable = GetDataAsTable(newValue, |
- | + | if reg.dataType == DATATYPE.DW and inversion then | |
- | local dataTable = GetDataAsTable(newValue, | + | |
- | + | | |
- | local coilsTmp = 0 | + | dataTableTmp[3]=dataTable[1]; |
- | if (device.name == " | + | dataTable=dataTableTmp |
- | coilsTmp = dataTable[2] * 0xFF | + | end |
- | dataTable[2] = dataTable[1] | + | |
- | dataTable[1] = coilsTmp | + | -- local coilsTmp = 0 |
- | end | + | if (device.name == " |
- | DEBUG("# | + | |
- | | + | dataTable[2] = dataTable[1] |
- | wrRequest[5] = dataTable[1] | + | dataTable[1] = coilsTmp |
- | wrRequest[6] = dataTable[2] | + | end |
- | + | wrRequest[DATA_LEN_HI] = dataTable[1] | |
- | -- CRC | + | wrRequest[DATA_LEN_LO] = dataTable[2] |
local crc, crcLo, crcHi = 0,0,0 | local crc, crcLo, crcHi = 0,0,0 | ||
crc = GetCRC(wrRequest, | crc = GetCRC(wrRequest, | ||
crcLo = GetLoByte(crc) | crcLo = GetLoByte(crc) | ||
- | crcHi = GetHiByte(crc) | + | crcHi = GetHiByte(crc) |
- | wrRequest[7] = crcLo | + | wrRequest[CRC_POS_LO] = crcLo |
- | wrRequest[8] = crcHi | + | wrRequest[CRC_POS_HI] = crcHi |
- | + | ||
- | DEBUG(" | + | |
local res = sendBytes(wrRequest); | local res = sendBytes(wrRequest); | ||
if (res == false) then | if (res == false) then | ||
Line 186: | Line 245: | ||
return false; | return false; | ||
end | end | ||
- | | + | |
- | -- читаем ответ | + | -- read response |
res = readBytes(8) | res = readBytes(8) | ||
- | | + | |
if (res == false) then | if (res == false) then | ||
DEBUG(" | DEBUG(" | ||
return false | return false | ||
end | end | ||
- | | + | |
if (table.concat(res) ~= table.concat(wrRequest)) then | if (table.concat(res) ~= table.concat(wrRequest)) then | ||
DEBUG(" | DEBUG(" | ||
return false | return false | ||
end | end | ||
- | | + | |
- | if (#dataTable == 4) then | + | if (#dataTable == DATALEN.DW) then |
- | -- DWORD | + | -- TRACE(" |
-- repeat steps for 2nd Word | -- repeat steps for 2nd Word | ||
- | wrRequest[3] = GetHiByte(reg.internalAddr + 1) | + | wrRequest[REG_ADDR_HI] = GetHiByte(reg.internalAddr + 1) |
- | wrRequest[4] = GetLoByte(reg.internalAddr + 1) | + | wrRequest[REG_ADDR_LO] = GetLoByte(reg.internalAddr + 1) |
- | wrRequest[5] = dataTable[3] | + | wrRequest[DATA_LEN_HI] = dataTable[3] |
- | wrRequest[6] = dataTable[4] | + | wrRequest[DATA_LEN_LO] = dataTable[4] |
- | crc, crcLo, crcHi = GetCRC(wrRequest, | + | |
- | wrRequest[7] = crcLo | + | |
- | wrRequest[8] = crcHi | + | |
+ | | ||
+ | wrRequest[CRC_POS_HI] = crcHi | ||
+ | | ||
res = sendBytes(wrRequest); | res = sendBytes(wrRequest); | ||
if (res == false) then | if (res == false) then | ||
Line 218: | Line 280: | ||
return true | return true | ||
end | end | ||
- | + | ||
- | + | ||
- | -- Calculating | + | -- Calculating |
function GetCRC(req, offset) | function GetCRC(req, offset) | ||
- | | + | |
local crc = 0xffff | local crc = 0xffff | ||
local mask = 0 | local mask = 0 | ||
- | | + | |
-- iterate bytes | -- iterate bytes | ||
for i=1,# | for i=1,# | ||
Line 238: | Line 300: | ||
crc = bit.rshift(crc, | crc = bit.rshift(crc, | ||
end | end | ||
- | | + | |
end | end | ||
end | end | ||
| | ||
end | end | ||
+ | |||
function GetHiByte(c) | function GetHiByte(c) | ||
- | | + | assert(c < 65536, "This is not a two bytes!" |
+ | -- DEBUG(" | ||
| | ||
end | end | ||
- | + | ||
- | function GetLoByte(input_val) | + | function GetLoByte(c) |
- | DEBUG(" | + | assert(c < 65536, "This is not a two bytes!" |
- | return bit.band(input_val,0xFF) | + | |
+ | return bit.band(c,0xFF) | ||
end | end | ||
- | + | ||
- | function | + | function |
- | -- get hex and concat it to number via string operatoin | + | local h1, h2 = {}, {} |
- | DEBUG(" | + | local half = math.ceil(# |
- | local numberAs_String | + | for i = 1, half do |
- | local tmpStr | + | table.insert(h1, full[i]) |
- | | + | end |
- | | + | for i = half+1, #full do |
- | | + | |
- | if (#tmpStr == 1) then | + | end |
- | tmpStr = " | + | return |
- | | + | |
- | | + | |
- | | + | |
- | DEBUG(" | + | |
- | return | + | |
end | end | ||
Line 274: | Line 333: | ||
local highWord, lowWord, tmpTable = 0, 0, {} | local highWord, lowWord, tmpTable = 0, 0, {} | ||
- | | + | |
- | if (datatype ~= 3) then | + | if (datatype ~= DATATYPE.DW) then |
- | DEBUG(" | + | DEBUG(" |
- | tmpTable[1] = GetHiByte(value) ; DEBUG(tmpTable[1]) | + | tmpTable[1] = GetHiByte(value) ; DEBUG(' |
- | tmpTable[2] = GetLoByte(value) ; DEBUG(tmpTable[2]) | + | tmpTable[2] = GetLoByte(value) ; DEBUG(' |
else | else | ||
- | highWord = bit.rshift(value, | + | highWord |
- | lowWord = bit.band(c,0xFFFF) | + | lowWord |
- | + | ||
tmpTable[1] = GetHiByte(highWord) | tmpTable[1] = GetHiByte(highWord) | ||
tmpTable[2] = GetLoByte(highWord) | tmpTable[2] = GetLoByte(highWord) | ||
tmpTable[3] = GetHiByte(lowWord) | tmpTable[3] = GetHiByte(lowWord) | ||
tmpTable[4] = GetLoByte(lowWord) | tmpTable[4] = GetLoByte(lowWord) | ||
+ | | ||
+ | DEBUG(" | ||
end | end | ||
return tmpTable | return tmpTable | ||
end | end | ||
+ | |||
+ | function toBits(num, | ||
+ | -- 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, | ||
+ | num = math.floor((num - t[b]) / 2) | ||
+ | end | ||
+ | return t | ||
+ | end | ||
+ | |||
+ | |||
+ | function Bin2Hex(s) | ||
+ | assert (type(s) == " | ||
+ | -- s -> binary string | ||
+ | local bin2hex = { | ||
+ | [" | ||
+ | [" | ||
+ | [" | ||
+ | [" | ||
+ | } | ||
+ | |||
+ | 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(" | ||
+ | end | ||
+ | |||
+ | for i = 1, l, 4 do | ||
+ | b = string.sub(s, | ||
+ | table.insert(tabBytes, | ||
+ | if not b then ERROR(" | ||
+ | TRACE(" | ||
+ | h = h..bin2hex[b] | ||
+ | end | ||
+ | |||
+ | return h, tabBytes | ||
+ | end | ||
+ | |||
+ | |||
+ | function hex2dec(hexstr) | ||
+ | return tonumber(hexstr, | ||
+ | end | ||
+ | |||
+ | function decodeIEEE754FloatToLua(input) | ||
+ | sign = input < 0 and -1 or 1 | ||
+ | input = math.abs(input) | ||
+ | | ||
+ | bitsTable = toBits(input, | ||
+ | | ||
+ | exponentTable={table.unpack(bitsTable, | ||
+ | mantissaTable={table.unpack(bitsTable, | ||
+ | |||
+ | mantissaStr, | ||
+ | mantissaNum = hex2dec(table.concat(manstissaTabBytes)) | ||
+ | |||
+ | |||
+ | exponentStr, | ||
+ | exponentNum = hex2dec(table.concat(exponentBytesTable)) | ||
+ | |||
+ | exponent = exponentNum - 127 -- 0 - 127 = -127 --> denormalized mode | ||
+ | mantissa=mantissaNum/ | ||
+ | |||
+ | if exponent == -127 then -- denormalized mode | ||
+ | exponent = -126 | ||
+ | mantissa = mantissa | ||
+ | else | ||
+ | mantissa = mantissa + 1 | ||
+ | end | ||
+ | |||
+ | float_number=math.ldexp(mantissa, | ||
+ | return float_number | ||
+ | end | ||
+ | |||
</ | </ | ||
modbus_rtu_custom.txt · Last modified: 2023/11/30 10:01 by emozolyak