Custom protocol implementation for Modbus RTU
Материал из WebHMI Wiki
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