Modbus RTU в виде custom protocol — различия между версиями
Материал из WebHMI Wiki
(Отметить эту версию для перевода) |
|||
Строка 1: | Строка 1: | ||
<translate> | <translate> | ||
+ | <!--T:1--> | ||
Пример реализации Modbus RTU в виде пользовательского протокола. | Пример реализации Modbus RTU в виде пользовательского протокола. | ||
+ | <!--T:2--> | ||
Type: Serial Port<br> | Type: Serial Port<br> | ||
Address validation: ^(C[0-9]+)$|^(DI[0-9]+)$|^(HR[0-9]+)$|^(IR[0-9]+)$<br> | Address validation: ^(C[0-9]+)$|^(DI[0-9]+)$|^(HR[0-9]+)$|^(IR[0-9]+)$<br> | ||
Validation error message: Invalid register address. Valid ModBus addresses are Cxxx, DIxxx, IRxxx, HRxxx.<br> | Validation error message: Invalid register address. Valid ModBus addresses are Cxxx, DIxxx, IRxxx, HRxxx.<br> | ||
+ | <!--T:3--> | ||
Code:<br> | Code:<br> | ||
+ | <!--T:4--> | ||
<syntaxhighlight lang = lua> | <syntaxhighlight lang = lua> | ||
-- MODBUS RTU Demo Driver | -- MODBUS RTU Demo Driver | ||
+ | <!--T:5--> | ||
function createDevices () | function createDevices () | ||
-- read FC write FC | -- read FC write FC | ||
Строка 20: | Строка 25: | ||
end | end | ||
+ | <!--T:6--> | ||
local errorCount = 0 | local errorCount = 0 | ||
+ | <!--T:7--> | ||
-- template | -- template | ||
local request = {1, 2, -- slaveId FC | local request = {1, 2, -- slaveId FC | ||
Строка 29: | Строка 36: | ||
} | } | ||
+ | <!--T:8--> | ||
EXCEPTIONS = {"Illegal Function", "Illegal Data Address", | EXCEPTIONS = {"Illegal Function", "Illegal Data Address", | ||
"Illegal Data Value", "Slave Device Failure", | "Illegal Data Value", "Slave Device Failure", | ||
Строка 36: | Строка 44: | ||
} | } | ||
+ | <!--T:9--> | ||
function readRegister (reg, device, unitId) | function readRegister (reg, device, unitId) | ||
--- FORMING REQUEST ----------- | --- FORMING REQUEST ----------- | ||
Строка 62: | Строка 71: | ||
crcHi = GetHiByte(crc) ; request[8] = crcHi | crcHi = GetHiByte(crc) ; request[8] = crcHi | ||
− | -- SENDING REQUEST | + | <!--T:10--> |
+ | -- SENDING REQUEST | ||
if not (sendBytes(request)) then | if not (sendBytes(request)) then | ||
DEBUG("Can't send bytes") | DEBUG("Can't send bytes") | ||
Строка 135: | Строка 145: | ||
return GetHexFromTable(respData) | return GetHexFromTable(respData) | ||
+ | <!--T:11--> | ||
end -- readRegister | end -- readRegister | ||
+ | <!--T:12--> | ||
function writeRegister (reg, device, unitId, newValue) | function writeRegister (reg, device, unitId, newValue) | ||
-- for read-only don't write | -- for read-only don't write | ||
Строка 219: | Строка 231: | ||
+ | <!--T:13--> | ||
-- Calculating CRC for RTU | -- Calculating CRC for RTU | ||
function GetCRC(req, offset) | function GetCRC(req, offset) | ||
Строка 243: | Строка 256: | ||
end | end | ||
+ | <!--T:14--> | ||
function GetHiByte(c) | function GetHiByte(c) | ||
DEBUG("Going to get high byte from "..c) | DEBUG("Going to get high byte from "..c) | ||
Строка 248: | Строка 262: | ||
end | end | ||
+ | <!--T:15--> | ||
function GetLoByte(input_val) | function GetLoByte(input_val) | ||
DEBUG("Going to get low byte from "..input_val) | DEBUG("Going to get low byte from "..input_val) | ||
Строка 253: | Строка 268: | ||
end | end | ||
+ | <!--T:16--> | ||
function GetHexFromTable(inputTab) | function GetHexFromTable(inputTab) | ||
-- get hex and concat it to number via string operatoin | -- get hex and concat it to number via string operatoin | ||
Строка 270: | Строка 286: | ||
end | end | ||
+ | <!--T:17--> | ||
function GetDataAsTable(value, datatype) | function GetDataAsTable(value, datatype) | ||
Строка 291: | Строка 308: | ||
+ | <!--T:18--> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
</translate> | </translate> |
Версия 14:51, 30 мая 2018
Пример реализации 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