Files
geedge-jira/attachment/39428/modbus_20230517.lua
2025-09-14 22:00:20 +00:00

579 lines
17 KiB
Lua

--
-- liuyognsheng 2023.4.20
--
-- port 44818 and ( UDP or TCP )
-- header size == 24
--
--
--
--
--
function loga(...)
if (run_mode == "debug") then
print(...)
else
APP.log_debug(...)
end
end
function APPdataDump()
--ret = "";
--for i = 1,#APP.data do
-- ret = ret..string.format("%02x ",string.byte(APP.data,i,i + 1))
--end
ret1,ret2 = string.byte(APP.data,5,7)
loga("APP.data:",type(ret1),ret1,ret2)
end
function file2byte(filename)
local file = io.open(filename, "rb")
if (file == nil ) then
return nil
end
io.input(file)
local ret = io.read("*a")
io.close(file)
return ret
end
function byte_unpack(a , b ,c)
--print(debug.traceback())
if (string.len(a) < c) then
return nil
end
return string.byte(a , b , c)
end
function byte_unpack_1(data)
local ret = byte_unpack(data , offset,offset)
offset = offset + 1
--loga("offset=",offset)
return ret
end
function byte_unpack_2(data)
--print(debug.traceback())
local ret1,ret2 = byte_unpack(data , offset , offset + 1)
local ret = ret1*256 + ret2
--loga(string.format("data[%d,%d] = %x",offset , offset +1,ret))
offset = offset + 2
return ret
end
function byte_unpack_4(data , offset)
local ret1,ret2,ret3,ret4 = byte_unpack(data , offset , offset + 3)
offset = offset + 4
local ret = ret1*256*256*256 + ret2*256*256 + ret3*256 + ret4
return ret
end
function Xor(num1,num2)
local tmp1 = num1
local tmp2 = num2
local str = ""
repeat
local s1 = tmp1 % 2
local s2 = tmp2 % 2
if s1 == s2 then
str = "0"..str
else
str = "1"..str
end
tmp1 = math.modf(tmp1/2)
tmp2 = math.modf(tmp2/2)
until(tmp1 == 0 and tmp2 == 0)
return tonumber(str,2)
end
function And(num1,num2)
local tmp1 = num1
local tmp2 = num2
local str = ""
repeat
local s1 = tmp1 % 2
local s2 = tmp2 % 2
if s1 == s2 then
if s1 == 1 then
str = "1"..str
else
str = "0"..str
end
else
str = "0"..str
end
tmp1 = math.modf(tmp1/2)
tmp2 = math.modf(tmp2/2)
until(tmp1 == 0 and tmp2 == 0)
return tonumber(str,2)
end
function Or(num1,num2)
local tmp1 = num1
local tmp2 = num2
local str = ""
repeat
local s1 = tmp1 % 2
local s2 = tmp2 % 2
if s1 == s2 then
if s1 == 0 then
str = "0"..str
else
str = "1"..str
end
else
str = "1"..str
end
tmp1 = math.modf(tmp1/2)
tmp2 = math.modf(tmp2/2)
until(tmp1 == 0 and tmp2 == 0)
return tonumber(str,2)
end
run_mode = "release"
D_prefix = "D"
local function str2data(str)
local i = 1;
local len = string.len(str)
local ret = {}
for pos = 1 , len ,2 do
ret[i] = tonumber(string.sub(str, pos,pos+1),16)
i = i + 1
end
return ret
end
local function datadump(indata)
for i, v in ipairs(indata) do
loga(indata[i])
end
end
-- create test env
local function testenv()
APP = { context = {appname = nil }}
--APP.data = str2data("6f0016000001021200000000d36d8601701ee10000000000000000000500020000000000b20006008e0000000200")
APP.data = file2byte("./modbus.bin")
APP.append_extra_info = loga
run_mode = "debug"
end
---------------------- Modbus start -----------------------
---
--- 0 1 2
-- unknow yes no
--
local app_define= {}
app_define["HEAD_LEN"] = 7
app_define["PDU_MIN_LEN"] = 2
app_define["PDU_MAX_LEN"] = 254
app_define["PORT"] = 502
app_define["FUNC"] = {}
app_define["FUNC"][0x01] = { desc="FUNC_READCOILS",read=1 , write=0}
app_define["FUNC"][0x02] = {desc="FUNC_READDISCINPUTS",read=1 , write=0}
app_define["FUNC"][0x03] = {desc="FUNC_READHOLDREGS",read=1 , write=0}
app_define["FUNC"][0x04] = {desc="FUNC_READINPUTREGS",read=1 , write=0}
app_define["FUNC"][0x05] = {desc="FUNC_WRITESINGLECOIL",read=0 , write=1}
app_define["FUNC"][0x06] = {desc="FUNC_WRITESINGLEREG",read=0 , write=1}
app_define["FUNC"][0x07] = {desc="FUNC_READEXCSTATUS"}
app_define["FUNC"][0x08] = {desc="FUNC_DIAGNOSTIC"}
app_define["FUNC"][0x0b] = {desc="FUNC_GETCOMEVTCOUNTER"}
app_define["FUNC"][0x0c] = {desc="FUNC_GETCOMEVTLOG"}
app_define["FUNC"][0x0f] = {desc="FUNC_WRITEMULTCOILS",read=0 , write=1}
app_define["FUNC"][0x10] = {desc="FUNC_WRITEMULTREGS",read=0 , write=1}
app_define["FUNC"][0x11] = {desc="FUNC_REPORTSERVERID"}
app_define["FUNC"][0x14] = {desc="FUNC_READFILERECORD"}
app_define["FUNC"][0x15] = {desc="FUNC_WRITEFILERECORD"}
app_define["FUNC"][0x16] = {desc="FUNC_MASKWRITEREG",read=0 , write=1}
app_define["FUNC"][0x17] = {desc="FUNC_READWRITEMULTREGS",read=1 , write=1}
app_define["FUNC"][0x18] = {desc="FUNC_READFIFOQUEUE"}
app_define["FUNC"][0x2b] = {desc="FUNC_ENCAPINTTRANS"}
app_define["FUNC"][0x7f] = {desc="FUNC_MASK"}
app_define["FUNC"][0x80] = {desc="FUNC_ERRORMASK"}
app_define["SUB_FUNC"] = {
{func=0x00,desc="SUBFUNC_QUERY_DATA"},
{func=0x01,desc="SUBFUNC_RESTART_COM"},
{func=0x02,desc="SUBFUNC_DIAG_REGS"},
{func=0x03,desc="SUBFUNC_CHANGE_DELIMITER"},
{func=0x04,desc="SUBFUNC_LISTEN_MODE"},
{func=0x0a,desc="SUBFUNC_CLEAR_REGS"},
{func=0x0b,desc="SUBFUNC_BUS_MSG_COUNT"},
{func=0x0c,desc="SUBFUNC_COM_ERR_COUNT"},
{func=0x0d,desc="SUBFUNC_EXCEPT_ERR_COUNT"},
{func=0x0e,desc="SUBFUNC_SERVER_MSG_COUNT"},
{func=0x0f,desc="SUBFUNC_SERVER_NO_RSP_COUNT"},
{func=0x10,desc="SUBFUNC_SERVER_NAK_COUNT"},
{func=0x11,desc="SUBFUNC_SERVER_BUSY_COUNT"},
{func=0x12,desc="SUBFUNC_SERVER_CHAR_COUNT"},
{func=0x14,desc="SUBFUNC_CLEAR_COUNT"},
}
local function is_modbus()
if (APP.context.appname == nil) then
return 0
end
if (APP.context.not_modbus== 1) then
return 2
elseif (APP.context.appname == "modbus") then
return 1
end
return 0
end
-- 合法性检测
local function ModbusChk()
if (APP.context.seq == nil) then
APP.context.seq = 1
else
APP.context.seq = APP.context.seq + 1
end
if (APP.data == nil or string.len(APP.data) < app_define["HEAD_LEN"]) then
loga("not modbus !!!")
APP.context.not_modbus = 1
return false
end
return true
end
local function ModbusDumpData(tx)
local ret = ""
--print(debug.traceback(),tx.data,tx.data["D1"])
for i, v in pairs(tx.data) do
--loga("ModbusDumpData in for",i,v)
ret = ret..(string.format("%02x",tx.data[i]))
end
return ret
end
local function ModbusParseHeader()
local header = {}
header.transaction_id = byte_unpack_2(APP.data)
header.protocol_id = byte_unpack_2(APP.data)
header.length = byte_unpack_2(APP.data)
header.unit_id = byte_unpack_1(APP.data)
loga(string.format("transaction_id %x protocol_id %x length %x unit_id %x offset:%d",header.transaction_id,header.protocol_id,header.length,header.unit_id,offset))
return header
end
local function ModbusCheckHeader( header )
if (header.protocol_id ~= 0) then
modbus.event[#modbus.event + 1] = "invalid protocol id"
end
if ((header.length < app_define["PDU_MIN_LEN"])
or (header.length > app_define["PDU_MAX_LEN"])) then
loga(header.length)
modbus.event[#modbus.event + 1] = "invalid length"
end
if ((header.unit_id > 247) and (header.unit_id < 255) ) then
modbus.event[#modbus.event + 1] = "invalid unit identifier"
end
end
local function ModbusParseReadRequest()
local ret = 0
modbus.tx.read = {}
modbus.tx.read.address = byte_unpack_2(APP.data)
modbus.tx.read.quantity = byte_unpack_2(APP.data)
if (And(modbus.tx.read.quantity , 0x0c) ~= 0 ) then
if (modbus.tx.read.quantity == 0 or modbus.tx.read.quantity > 2000) then
ret = 1
end
else
if (modbus.tx.read.quantity == 0 or modbus.tx.read.quantity > 125) then
ret = 1
end
end
if (ret == 1) then
modbus.event[#modbus.event + 1] = "invalid decoder invalid value"
end
end
local function ModbusParseWriteRequest()
modbus.tx.write = {}
modbus.tx.write.count = 0
modbus.tx.write.address = byte_unpack_2(APP.data)
loga("modbus.tx.write.address:",modbus.tx.write.address)
loga("modbus.tx.func:",modbus.tx.func)
if (string.find(app_define["FUNC"][modbus.tx.func].desc,"SINGLE") ~= nil) then
modbus.tx.write.quantity = 1
elseif (string.find(app_define["FUNC"][modbus.tx.func].desc,"MUL") ~= nil) then
modbus.tx.write.quantity = byte_unpack_2(APP.data)
modbus.tx.write.count = byte_unpack_1(APP.data , offset)
if ( modbus.tx.write.quantity == nil or modbus.tx.write.count == nil) then
modbus.event[#modbus.event + 1] = "invalid decoder invalid value"
return
end
else
modbus.tx.write.quantity = 2
end
--get data
modbus.tx.data = {}
-- get last info , max 20
for key_name = 1,20 do
local tmp = byte_unpack_1(APP.data , offset)
if (tmp == nil) then
break
end
--loga("key_name:",key_name , "tmp:",tmp)
modbus.tx.data[D_prefix..key_name] = tmp
end
loga(ModbusDumpData(modbus.tx))
return
--[[
if (string.find(app_define["FUNC"][modbus.tx.func].desc,"COILS") ~= nil) then
if (string.find(app_define["FUNC"][modbus.tx.func].desc,"SINGLE") ~= nil) then
tx.data[D_prefix..key_name] = byte_unpack_2(APP.data)
key_name = key_name + 1
loga("1")
else
for pos=1 , modbus.tx.write.count do
modbus.tx.data[D_prefix..key_name] = byte_unpack_1(APP.data , offset)
key_name = key_name + 1
loga("2")
end
end
else
for pos=1 , modbus.tx.write.count do
modbus.tx.data[D_prefix..key_name] = byte_unpack_2(APP.data)
key_name = key_name + 1
loga("3")
end
end
ModbusDumpData(modbus.tx)
]]
end
local function ModbusParseRequestPDU()
modbus.tx.func = byte_unpack_1(APP.data , offset)
loga("modbus.tx.func:",modbus.tx.func)
if (modbus.tx.func == 0x00) then
modbus.event[#modbus.event + 1] = "invalid function code"
return false
elseif (app_define["FUNC"][modbus.tx.func] == nil) then
modbus.event[#modbus.event + 1] = "invalid function code"
return false
elseif ((app_define["FUNC"][modbus.tx.func].desc == "FUNC_READFILERECORD"
or app_define["FUNC"][modbus.tx.func].desc == "FUNC_WRITEFILERECORD") ) then
local tmp1 = byte_unpack_1(APP.data , offset)
elseif (app_define["FUNC"][modbus.tx.func].desc == "FUNC_DIAGNOSTIC" ) then
modbus.tx.subfunc = byte_unpack_2(APP.data)
local data = byte_unpack_2(APP.data)
elseif (app_define["FUNC"][modbus.tx.func].desc == "FUNC_ENCAPINTTRANS" ) then
local tmp1 = byte_unpack_1(APP.data , offset)
end
if (app_define["FUNC"][modbus.tx.func].read == 1) then
ModbusParseReadRequest()
modbus.tx.action = "read"
end
if (app_define["FUNC"][modbus.tx.func].write == 1) then
ModbusParseWriteRequest()
if (modbus.tx.action == nil) then
modbus.tx.action = "write"
else
modbus.tx.action = "|write"
end
end
return true
end
local function ModbusParseReadResponse()
modbus.tx.read = {}
modbus.tx.read.count = byte_unpack_1(APP.data , offset)
if (modbus.tx.read.count == nil ) then
modbus.event[#modbus.event + 1] = "invalid decoder invalid value"
return
end
loga("ModbusParseReadResponse",modbus.tx.read.count,modbus.req_tx.read.quantity)
if (modbus.tx.read.count == 2*modbus.req_tx.read.quantity
or modbus.tx.read.count == (modbus.req_tx.read.quantity + 7)/8) then
modbus.tx.data = {}
loga("modbus.tx.read.count :",modbus.tx.read.count)
if (modbus.tx.func == 1 or modbus.tx.func == 2) then
for i = 1 , modbus.tx.read.count do
modbus.tx.data[#modbus.tx.data + 1] = byte_unpack_1(APP.data)
end
else
for i = 1 , modbus.tx.read.count/2 do
--loga("#modbus.tx.data:",#modbus.tx.data)
modbus.tx.data[#modbus.tx.data + 1] = byte_unpack_2(APP.data)
end
end
end
end
local function ModbusParseWriteResponse()
modbus.tx.write = {}
modbus.tx.write.address = byte_unpack_2(APP.data)
if (modbus.tx.write.address ~= modbus.req_tx.write.address) then
modbus.event[#modbus.event + 1] = "invalid decoder invalid address"
return
end
end
local function ModbusParseResponsePDU()
modbus.tx.func = byte_unpack_1(APP.data)
if (modbus.tx.func == 0x00) then
modbus.event[#modbus.event + 1] = "invalid function code"
return
elseif (app_define["FUNC"][modbus.tx.func] == nil) then
modbus.event[#modbus.event + 1] = "invalid function code"
return
elseif ( app_define["FUNC"][modbus.tx.func].desc == "FUNC_READFILERECORD"
or app_define["FUNC"][modbus.tx.func].desc == "FUNC_WRITEFILERECORD" ) then
local tmp1 = byte_unpack_1(APP.data , offset)
end
if (app_define["FUNC"][modbus.tx.func].read == 1) then
ModbusParseReadResponse()
modbus.tx.action = "read"
end
if (app_define["FUNC"][modbus.tx.func].write == 1) then
ModbusParseWriteResponse()
if (modbus.tx.action == nil) then
modbus.tx.action = "write"
else
modbus.tx.action = "|write"
end
end
end
local function ModbustxFindbyid(findid)
if (APP.context.modbus == nil ) then
loga("APP.context.modbus == nil")
return nil
end
loga("findid : ", findid)
for k , v in pairs(APP.context.modbus) do
loga("findidBy:", k ,type(v.tx.header.transaction_id)," id:", v.tx.header.transaction_id)
if (findid == v.tx.header.transaction_id) then
return v.tx
end
end
return nil
end
local function ModbusUpdate(modbus)
for k , v in pairs(APP.context.modbus) do
if (modbus.tx.header.transaction_id == v.tx.header.transaction_id) then
loga("ModbusUpdate ",modbus.tx.func)
APP.context.modbus[k] = modbus
end
end
end
local function ModbusAddtx(modbus)
local search_tx = ModbustxFindbyid(modbus.tx.header.transaction_id)
loga("ModbusAddtx",modbus.tx.func)
if (APP.context.modbus == nil) then
APP.context.modbus = {}
APP.context.modbusNum = 1
elseif (search_tx == nil) then
APP.context.modbusNum = APP.context.modbusNum + 1
else
ModbusUpdate(modbus)
return
end
APP.context.modbus["m"..APP.context.modbusNum] = modbus
loga("ModbusAddtx ",APP.context.modbusNum ," APP.context.modbus ")
if (APP.context.modbus == nil) then
loga("APP.context.modbus == nil")
else
loga("tx.header.transaction_id: ",APP.context.modbus.m1.tx.header.transaction_id)
end
end
local function ModbusParseRequest()
if (not ModbusChk()) then
return false
end
modbus = {tx = {},event = {}}
header = ModbusParseHeader()
ModbusCheckHeader(header)
if (#modbus.event >0) then
loga(modbus.event[1])
return false
end
modbus.tx.header = header
ModbusParseRequestPDU()
if (#modbus.event >0) then
return false
end
ModbusAddtx(modbus)
return true
end
local function ModbusParseResponse()
if (not ModbusChk()) then
return false
end
modbus = {tx = {},event = {}}
header = ModbusParseHeader()
ModbusCheckHeader(header)
if (#modbus.event >0) then
return false
end
-- find tx
tx = ModbustxFindbyid(header.transaction_id)
if (tx == nil ) then
loga("Error: not find transaction id")
return false
end
modbus.req_tx = tx
modbus.tx.length = header.length
ModbusParseResponsePDU()
if (#modbus.event >0) then
loga("Error : ",modbus.event[1])
return false
end
APP.append_extra_info("app_type","Modbus")
APP.append_extra_info("action",modbus.req_tx.action)
--loga("###################")
if (string.find(modbus.tx.action , "read") ~= nil) then
APP.append_extra_info("address",modbus.req_tx.read.address)
APP.append_extra_info("quantity",modbus.req_tx.read.quantity)
APP.append_extra_info("data",ModbusDumpData(modbus.tx))
else
APP.append_extra_info("address",modbus.tx.write.address)
APP.append_extra_info("quantity",modbus.req_tx.write.quantity)
APP.append_extra_info("data",ModbusDumpData(modbus.req_tx))
end
return true
end
---------------------- Modbus end -----------------------
--testenv()
--for APP.data
--datadump(APP.data)
--
--miss null data
if (string.len(APP.data) == 0) then
return true
end
offset = 1
if(APP.get_payload_direction()==1) then
--loga("request")
return ModbusParseRequest()
elseif (APP.get_payload_direction()==2) then
--loga("response")
return ModbusParseResponse()
end