require "internal_api" require "log" require "sys" require "utils" require "patch" require "fs" siganl = require 'posix.signal' syswait = require 'posix.sys.wait' unistd = require 'posix.unistd' mqtt = require "mosquitto" lpack = require("lua_pack") base64 = require("base64") dyutils = require("dyutils") math = require("math") cjson = require("cjson") require "lsqlite3" bit=require("bit") bnot = bit.bnot band, bor, bxor = bit.band, bit.bor, bit.bxor lshift, rshift, rol = bit.lshift, bit.rshift, bit.rol --- 数据解析后处理函数 - 转成数字 -- @string input, 数据内容,例如 01020304 -- @number size, 数据长度 -- @return number,转换后的数据 -- @usage PostFunc_ToNumber(string.sub(one_data['Data'], offset + 1, offset + size), size) function PostFunc_ToNumber(input, size) local result = 0 for i=1,size do result = lshift(result, 8) + string.byte(input, i, i) end return result end --- 数据解析后处理函数 - 转成base64的字符串 -- @string input, 数据内容,例如 05060708 -- @number size, 数据长度 -- @return string, base64之后的数据 function PostFunc_Base64_Encode(input, size) print(dyutils.bin_str_to_hex(input)) local result = base64.encode(input) return result end --- 数据解析后处理函数 - 转成数字,并进行计算 -- @string input, 数据内容,例如 01020304 -- @number size, 数据长度 -- @return number,转换后的数据 function PostFunc_Calc_Temp(input, size) local r = PostFunc_ToNumber(input, size) r = r * 0.1 - 40 return r end -- Memory Areas -- 0x03 - System info of S200 family -- 0x05 - System flags of S200 family -- 0x06 - Analog inputs of S200 family -- 0x07 - Analog outputs of S200 family -- 0x1C - S7 counters (C) -- 0x1D - S7 timers (T) -- 0x1E - IEC counters (200 family) -- 0x1F - IEC timers (200 family) -- 0x80 - Direct peripheral access (P) -- 0x81 - Inputs (I) -- 0x82 - Outputs (Q) -- 0x83 - Flags (M) (Merker) -- 0x84 - Data blocks (DB) -- 0x85 - Instance data blocks (DI) -- 0x86 - Local data (L) -- 0x87 - Unknown yet (V) -- WordLen -- S7WLBit 0x01 Bit (inside a word) -- S7WLByte 0x02 Byte (8 bit) -- S7WLWord 0x04 Word (16 bit) -- S7WLDWord 0x06 Double Word (32 bit) -- S7WLReal 0x08 Real (32 bit float) -- S7WLCounter 0x1C Counter (16 bit) -- S7WLTimer 0x1D Timer (16 bit) -- 这里的serive必须和协议模板里面的标识符和协议里面的topic一致 -- Area和WordLen字段感觉上面注释的内容填写 -- PostFunc为接收到数据后的后处理函数,当不设置时,默认采用PostFunc_ToNumber作为处理函数 service_table = { { service = "read_DB", data_table = { operation = "read", areas = { { id = "CurA", Area = 0x84, DBNumber = 1, Start = 0, Amount = 4, WordLen = 0x02, PostFunc = PostFunc_ToNumber }, { id = "CurB", Area = 0x84, DBNumber = 1, Start = 4, Amount = 4, WordLen = 0x02, PostFunc = PostFunc_ToNumber }, { id = "CurC", Area = 0x84, DBNumber = 1, Start = 8, Amount = 4, WordLen = 0x02 }, { id = "VolA", Area = 0x84, DBNumber = 1, Start = 0x40, Amount = 4, WordLen = 0x02 }, { id = "VolB", Area = 0x84, DBNumber = 1, Start = 0x44, Amount = 4, WordLen = 0x02 }, { id = "VolC", Area = 0x84, DBNumber = 1, Start = 0x48, Amount = 4, WordLen = 0x02 }, { id = "Temp", Area = 0x84, DBNumber = 1, Start = 0x50, Amount = 4, WordLen = 0x02, PostFunc = PostFunc_Calc_Temp }, { id = "Temp2", Area = 0x84, DBNumber = 1, Start = 0x60, Amount = 4, WordLen = 0x02, PostFunc = PostFunc_Calc_Temp }, { id = "Temp3", Area = 0x84, DBNumber = 2, Start = 0x64, Amount = 0x60, WordLen = 0x02, PostFunc = PostFunc_Base64_Encode } } } }, { service = "read_temp", data_table = { operation = "read", areas = { { id = "CurA", Area = 0x82, DBNumber = -1, Start = 0, Amount = 4, WordLen = 0x02 }, { id = "CurB", Area = 0x82, DBNumber = -1, Start = 4, Amount = 4, WordLen = 0x02 }, { id = "CurC", Area = 0x82, DBNumber = -1, Start = 8, Amount = 4, WordLen = 0x02 } } } }, { service = "write_db1", data_table = { operation = "write", areas = { { id = "AA", Area = 0x84, DBNumber = 1, Start = 0, Amount = 4, WordLen = 0x02 }, { id = "BB", Area = 0x84, DBNumber = 1, Start = 4, Amount = 4, WordLen = 0x02 } } } } } MAX_HOLE_SIZE = 8 down_table = nil function get_size_from_wordlen(WordLen) if WordLen == 0x1 then return 1 elseif WordLen == 0x02 then return 1 elseif WordLen == 0x04 then return 2 elseif WordLen == 0x06 then return 4 elseif WordLen == 0x08 then return 4 elseif WordLen == 0x1c then return 2 elseif WordLen == 0x1D then return 2 else return -1; end end function print_r ( t ) local print_r_cache={} local function sub_print_r(t,indent) if (print_r_cache[tostring(t)]) then print(indent.."*"..tostring(t)) else print_r_cache[tostring(t)]=true if (type(t)=="table") then for pos,val in pairs(t) do if (type(val)=="table") then print(indent.."["..pos.."] => "..tostring(t).." {") sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) print(indent..string.rep(" ",string.len(pos)+6).."}") elseif (type(val)=="string") then print(indent.."["..pos..'] => "'..val..'"') else print(indent.."["..pos.."] => "..tostring(val)) end end else print(indent..tostring(t)) end end end if (type(t)=="table") then print(tostring(t).." {") sub_print_r(t," ") print("}") else sub_print_r(t," ") end print() end --- 解析服务表,合并相邻数据 function analysis_down_area(service_table) if down_table ~= nil then print("already analysised") syslog.syslog("LOG_DEBUG", "already analysised") return 0 end down_table = {} for i, v in pairs(service_table) do if v['data_table']['operation'] == 'read' then local service_data = {} service_data['service'] = v['service'] service_data['data_table'] = {} service_data['data_table']['operation'] = v['data_table']['operation'] local down_data = {} for _i, Area in pairs(v['data_table']['areas']) do local find_match = 0 --print("analysing Area:"..Area['Area'].." DBNumber:"..Area['DBNumber'].." Start:"..Area['Start'].." Amount:"..Area['Amount']) for __i, down in pairs(down_data) do if down['Area'] == Area['Area'] and down['DBNumber'] == Area['DBNumber'] and down['WordLen'] == Area['WordLen'] then --print("\tmatching Area:"..down['Area'].." DBNumber:"..down['DBNumber'].." Start:"..down['Start'].." Amount:"..down['Amount']) if ((down['Start'] + down['Amount']) <= Area['Start']) and ((down['Start'] + down['Amount'] + MAX_HOLE_SIZE) >= Area['Start']) then down['Amount'] = down['Amount'] + (Area['Start'] - (down['Start'] + down['Amount'])) + Area['Amount'] find_match = 1 break end end end if find_match == 0 then local once = {} once['Area'] = Area['Area'] once['DBNumber'] = Area['DBNumber'] once['Start'] = Area['Start'] once['Amount'] = Area['Amount'] once['WordLen'] = Area['WordLen'] table.insert(down_data, once) end end service_data['data_table']['areas'] = down_data table.insert(down_table, service_data) elseif v['data_table']['operation'] == 'write' then local service_data = {} service_data['service'] = v['service'] service_data['data_table'] = {} service_data['data_table']['operation'] = v['data_table']['operation'] local down_data = {} for _i, Area in pairs(v['data_table']['areas']) do local once = {} once['Area'] = Area['Area'] once['DBNumber'] = Area['DBNumber'] once['Start'] = Area['Start'] once['Amount'] = Area['Amount'] once['WordLen'] = Area['WordLen'] once['id'] = Area['id'] table.insert(down_data, once) end service_data['data_table']['areas'] = down_data table.insert(down_table, service_data) end end print_r(down_table) end function protocol_encode(json_str, len) local err_code = 0 -- 取出入参-- local obj = cjson.decode(json_str) local id = obj.identifier local bin_str = nil analysis_down_area(service_table) syslog.syslog("LOG_DEBUG", "S7 PLC encode:" .. json_str) print(bin_str) for i, v in pairs(down_table) do if (v["service"] == id) then if (v['data_table']['operation'] == 'read') then bin_str = cjson.encode(v['data_table']) elseif (v['data_table']['operation'] == 'write') then for _i, area in pairs(v['data_table']['areas']) do if obj[area['id']] ~= nil then local size = get_size_from_wordlen(area['WordLen']) * area['Amount'] if (size == 4) then tmp = bpack(">I", obj[area['id']]) elseif (size == 2) then tmp = bpack(">S", obj[area['id']]) elseif (size == 1) then tmp = bpack("C", obj[area['id']]) end area['Data'] = base64.encode(tmp) end end bin_str = cjson.encode(v['data_table']) end break end end -- 出参格式化-- local t_str = '' for i = 1, #bin_str do t_str = t_str .. (string.format("%02x", string.byte(string.sub(bin_str, i, i)))) end bin_str = t_str print(err_code, bin_str, #bin_str) return err_code, bin_str, #bin_str end function protocol_decode(input_str, len) local err_code = 0 -- 取出入参-- local obj = {} local input_obj = cjson.decode(input_str) local bin_str = input_obj.raw_data local id = input_obj.identifier obj.identifier = id local t_str='' for i=1,#bin_str,2 do t_str=t_str .. string.char(tonumber(string.sub(bin_str, i, i+1),16)) end bin_str=t_str print(bin_str) local data = cjson.decode(bin_str) for i, v in pairs(data) do if v['Data_B64'] ~= nil then v["Data"] = base64.decode(v['Data_B64']) end end for i, v in pairs(service_table) do if id == v['service'] then for _i, Area in pairs(v['data_table']['areas']) do for __i, one_data in pairs(data) do if Area['Area'] == one_data['Area'] and Area['DBNumber'] == one_data['DBNumber'] and Area['Start'] >= one_data['Start'] and Area['Start'] + Area['Amount'] <= one_data['Start'] + one_data['Amount'] then if v['data_table']['operation'] == 'write' then obj[Area['id']..'_Result'] = one_data['Result'] elseif one_data['Result'] == 0 then local size = get_size_from_wordlen(Area['WordLen']) * Area['Amount'] local offset = (Area['Start'] - one_data['Start']) * get_size_from_wordlen(Area['WordLen']) if Area['PostFunc'] == nil then obj[Area['id']] = PostFunc_ToNumber(string.sub(one_data['Data'], offset + 1, offset + size), size) else obj[Area['id']] = Area['PostFunc'](string.sub(one_data['Data'], offset + 1, offset + size), size) end end break end end end break end end -- 出参格式化-- local json_str = cjson.encode(obj) --print(err_code, json_str, #json_str) return err_code, json_str, #json_str end