require "log" require "sys" require "utils" require "patch" -- require "pack" siganl = require 'posix.signal' syswait = require 'posix.sys.wait' unistd = require 'posix.unistd' mqtt=require "mosquitto" base64=require("base64") dyutils=require("dyutils") -- socket=require("socket_luat") require("lua_pack") require "internal_api" require"lsqlite3" RACK_PATH = "/app/" LOG_LEVEL = log.LOGLEVEL_TRACE gw_sn=nil service_info = {} message_cache = {} period_list = {} dev_count = 1 nodes = {} fuel = { System= { }, Fuel= { }, BatteryStack= { }, Inverter= { }, Energy= { }, Hot={ } } fuel2 = { System= {Status=0,EnvTemp=0,TotalGenTime=0,TotalGenCount=0 }, Fuel= { }, BatteryStack= { }, Inverter= {OutPower=0,OutVoltage=0 }, Energy= { ElectricityEnergy=0 ,LoadPower=0,GridPower=0}, Hot={ } } ------------------------------------------------------------------ ---------------------------- 业务触发配置表描述 ------------------------- ------------------------------------------------------------------ global_point_table = { { identifier = "FuelEnergy", ---- 采集表寄存器 regs = { { ----! regs.reginfo 寄存器名称(或名称的数组),寄存器地址,寄存器格式化符号,数值缩放比例 reginfo = {"Status",332,"I>I>I",stx,dir,model,old,lng,no,data1,data2) ---- 计算校验值计算出来是错的 boc = 0 for i = 1, #raw_data do boc = bit.bxor(boc,raw_data:byte(i)) end raw_data = raw_data .. bpack("C",bit.band(boc,0xff)) raw_data = raw_data .. bpack("C",etx) end return raw_data end ---- 解包二进制数据流,检查格式 ---- param@raw_data:二进制数据 function ex_unpack(raw_data) --取出入参-- local obj={} ---- 检查数据长度 if #raw_data < 400 then log.warn("ex_unpack", "raw data length error. length = ",#raw_data) return end local stx = string.byte(raw_data,1) local dir = string.byte(raw_data,2) local etx = string.byte(raw_data,#raw_data) ---- 检查头尾 if stx ~= 2 or etx ~= 3 then log.warn("ex_unpack", "STX or EXT error. STX,ETX = ",stx,etx) return end ---- 检查方向 -- if dir ~= 0x10 then -- log.warn("ex_unpack", "DIR error. STX,ETX = ",dir) -- return -- end return true,raw_data end local function online_status(sn) return true -- local timeinfo = pack_connect_obj[sn] -- if not timeinfo.lastrecv then return false end -- if os.difftime(os.time(),timeinfo.lastrecv) < 120 then -- return true -- end -- return false end local function FormatLenght(format) local len if string.upper(format) == "C" then len = 1 elseif string.upper(string.sub(format,2,2)) == "S" then len = 2 elseif string.upper(string.sub(format,2,2)) == "I" then len = 4 elseif string.upper(string.sub(format,1,1)) == "A" then len = tonumber(string.sub(format,2)) else log.warn("port2pp", "format not support",format) return 0 end return len end local function service_response(gw_sn,dev_sn,port,identifier,param,mi,time) local topic = string.format( "/sys/11320465/%s/service",gw_sn) local obj_send={} local json_str obj_send['mi'] = mi or 0 -- obj_send['len'] = #data obj_send['identifier'] = identifier obj_send['sn'] = dev_sn obj_send['time'] = time or os.time() -- obj_send["data_type"] = 2 obj_send["tags"] = param and type(param) == "table" and param or cjson.decode(param) json_str = cjson.encode(obj_send) log.info("service_response",topic,json_str) client:publish(topic, json_str) end ---- 二进制数据截取,并转为结构体 ---- param@uid:设备连接通道ID ---- param@raw_data:设备连接通道ID local function port2pp(uid,raw_data) print("response_message:",string.toHex(raw_data," ")) local result,data = ex_unpack(raw_data) if result then -- local dev_sn = FetchSnByAddr(addr) -- if not dev_sn then -- log.warn("port2pp", "SN not found") -- return -- end ---- 遍历配置 for _,service in ipairs(global_point_table) do for _,reg in ipairs(service.regs) do local key = reg.reginfo[1] local offset = reg.reginfo[2] local format = reg.reginfo[3] local scale = reg.reginfo[4] and reg.reginfo[4] or 1 local len = FormatLenght(format) ---- 检查数据点位是否存在 if not string.sub(data,offset + 1) then log.warn("port2pp","sample point error, key:",key,"point:", offset) break end ---- 将收到数据,按照点位偏移取出数值 local _,value = bunpack(string.sub(data,offset + 1),format) ---- 检查数据格式,如果是数值需要乘倍数 if type(value) == "number" then reg.parent[key] = value * scale else reg.parent[key] = value end end end -- local Inverter = fuel.Inverter -- local BatteryStack = fuel.BatteryStack -- if Inverter.OutPower and Inverter.OutVoltage then -- if Inverter.OutVoltage == 0 then -- BatteryStack.Current = 0 -- else -- local offset = Inverter.OutPower/ Inverter.OutVoltage -- BatteryStack.Current = offset-(offset%0.1) -- end -- end -- BatteryStack.Voltage = BatteryStack.Voltage-(BatteryStack.Voltage%0.1) log.info("port2pp","result:",cjson.encode(fuel)) return fuel end end ---- 初始报文 function init_message(uid) local payload = ex_pack() log.info("init_message:",string.toHex(payload," ")) sys.publish("NET_SENT_RDY_" .. uid,payload) end ---- 通常报文 function common_message(uid,no) local payload = ex_pack(2) log.info("common_message:",topic,string.toHex(payload," ")) sys.publish("NET_SENT_RDY_" .. uid,payload) end local function iot2pp(json_str, len) local err_code=0 --取出入参-- log.info("iot2pp","input:"..json_str) local obj=cjson.decode(json_str) local identifier=obj.identifier local server_period=obj.server_period local dev_sn=obj.sn --编码-- if obj.identifier == "null" then log.warn("iot2pp","Need device sn in payload") return 0, "", 0 end if obj.identifier == "ClusterInfo" then end return -1, nil, 0 end function on_external_msg(topic, payload) if string.find(topic,"service/set") then local ret,err = pcall(iot2pp,payload,#payload) if not ret then log.warn("mqtt.recv",err) end else log.info("due message:",topic, payload) end end local function parser_period_service() for _,node in ipairs(nodes) do for _,service in ipairs(global_point_table) do if service.read_write == "R" and service.sample_interval and service.sample_interval > 0 then local period={ identifier = service.identifier, sn = node.sn, intv_sample = service.sample_interval, -- 直接使用上报周期做采样 intv_report = service.report_interval, -- 直接使用上报周期做采样 last_sample = 0, last_report = 0 } table.insert(period_list,period) end end end end local function handler() local pid, status, code = syswait.wait(-1, syswait.WNOHANG) log.error('system exit', pid, status, code) --打印不出来 os.exit(0) end ---- 建立TCP连接 local function tcpTask(ping, timeout, addr, port, uid, login) local recvstamp = {} cid, timeout, uid = tonumber(cid) or 1, tonumber(timeout) or 120, tonumber(uid) or 1 if not ping or ping == "" then ping = "0x00" end while true do local idx = 0 local c = socket.tcp(ssl and ssl:lower() == "ssl") while not c:connect(addr, port) do sys.wait((2 ^ idx) * 1000)idx = idx > 9 and 0 or idx + 1 end recvstamp[cid] = os.time() init_message(0) ---- 发送初始报文,才回应 while true do local result, data, param = c:recv(timeout * 1000, "NET_SENT_RDY_" .. uid) log.info("transparent","result, data, param",result, data, param) if result then --收到socket消息 -- 这里执行用户自定义的指令 recvstamp[cid] = os.time() -- log.info("socket","recv:",data:toHex()) -- sys.publish("NET_RECV_WAIT_" .. uid, c.self.address ,c.self.port , data,cid) local fuel = port2pp(uid,data) if fuel then service_response(gw_sn,"2188050000B1_1","TCP","FuelEnergy",fuel,0) service_response(gw_sn,"2188050000B1_2","TCP","FuelEnergy",fuel,0) end elseif data == ("NET_SENT_RDY_" .. uid) then --收到系统消息 -- 转换为Hex String 报文 if not c:send((param)) then log.warn("socket","send failed:",param:toHex()) break end -- sys.wait(500) -- log.info("socket","sent:",data:toHex()) elseif data == "timeout" then if not c:send(ping) then log.warn("socket", "send timeout",c:send(ping)) break end if os.difftime(os.time(),recvstamp[cid]) > 2 * 60 * 60 then log.error("system", "socket state error,last recv time:",recvstamp[cid]) break end else log.error("socket", "unknow error") break end data, param = nil,nil end c:close() sys.wait(5000) end end function node_status() while true do sys.wait(3000) -- for sn,status in pairs(nodes_status) do -- for k, v in pairs(status) do -- log.debug('Devices.printStatus', 'SN:', sn, k, v) -- end -- end local change = false for sn,status in pairs(nodes_status) do local offtime = 60*3 status.login_time = 0 if offtime == 0 or status.last_rcv and offtime and os.time() < (status.last_rcv + offtime) then status.online = true else status.online = false end -- if status.tx_cnt and status.tx_cnt == 0 then status.loss_ratio = 0 else status.loss_ratio = (status.tx_cnt - status.resp_cnt) * 100 / status.tx_cnt end -- 判断是否离线标记 if status.online ~= status.last_online then status.last_online = status.online change = true end end if not last_report then last_report = 0 end if change or os.difftime(os.time(),last_report) >= 3600 then last_report = os.time() local report = {} report.status={} for sn,status in pairs(nodes_status) do local sta = deepCopy(status) -- 去掉不用的变量 sta.last_online = nil sta.resp_cnt = nil table.insert(report.status,sta) end local str = cjson.encode(report) report = nil if str then local topic = string.format( "/sys/11320465/%s/nodes_status",gw_sn) client:publish(topic, str) end end end end function main(nodes_cfg,sn) log.info("main_loop", "=============== fuelEnergy RUN 2.0 ============") gw_sn = sn if not gw_sn then -- gw_sn = io.getGatewayID() gw_sn = "21012C00FFFF" end log.info("main_loop", string.format("Try Run At Shell\nsn='%s'\nnodes='%s'\nmain_loop(nodes,sn)",gw_sn or "NOT SN",nodes_cfg or "")) --创建Rack for i = 1,3 do table.insert(nodes,{sn = string.format("%s_%d","56789",i),term_addr = i}) end local uid = 0 siganl.signal(siganl.SIGKILL,handler) siganl.signal(siganl.SIGHUP,handler) parser_period_service() local subscribe={string.format("/sys/11320465/%s/service/set",gw_sn),"Front/218800002001_TCP50001/transprent"} internal_api.mqtt_session("mqtt.lnxall.com",3883,"localuser","dywl@galaxy",subscribe,on_external_msg) ---- 建立平台数据长传通道 -- sys.taskInit(tcpTask, "heartbeat", 120, "192.168.11.3", 30000, uid, login) ---- 建立设备连接通道 sys.taskInit(function () local last = os.time() local lastSqus while true do sys.wait(3000) ---- 30S 一次发送 if change or os.difftime(os.time(),last) >= 5 then last = os.time() ---- 发送通常报文 -- common_message(uid,no) -- init_message(0) ---- 发送初始报文,才回应 local data = io.readFile("/tmp/out.txt") if data then -- local squs = string.sub(data,0x27,0x27+3) -- if lastSqus ~= squs then log.info("length",#data) local raw_data = string.sub(data,0x37,0x37-1+406) if raw_data then local fuel = port2pp(0,raw_data) if fuel then service_response(gw_sn,"2188050000B1_1","TCP","FuelEnergy",fuel,0) service_response(gw_sn,"2188050000B1_2","TCP","FuelEnergy",fuel2,0) end end -- end -- lastSqus = squs end end end end) --启动系统框架 sys.init(0, 0) sys.run() end -- python /root/packet_cap.py tcp and ip[2:2] = 446 and src host 192.168.11.2 and src port 5000& main(arg[1],arg[2])