-- -- 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