-- -- 网关SN ,虚拟设备SN, PCS设备SN(非PCS物理地址) 通过MQTT抓取 -- -- BatteryId = 虚拟设备SN package.cpath = "./?.so;/usr/lib/lua/?.so;/usr/lib/lua/loadall.so;" package.path = "/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua;/usr/lib/lua/5.1/?.lua;/usr/lib/lua/5.1/?/init.lua;./?.lua;/usr/share/lua/?.lua;/usr/share/lua/?/init.lua;/usr/lib/lua/?.lua;/usr/lib/lua/?/init.lua;/app/panasonicIfu/?.lua" require "log" require "sys" require "utils" require "patch" require "cloud" syslog=require("syslog") syslog.openlog("PANASONIC", syslog.LOG_PERROR + syslog.LOG_ODELAY, "LOG_USER") syslog.syslog("LOG_INFO", "=============== PANASONIC IFU RUN ============") TIME_ZOOM= (8*60*60 + 0*60) ADJ_BATT_INTERVAL= 10*60 --动态修改BMS参数 RETRANS_INTERVAL= 1*60 --重传间隔 -- ADJ_BATT_INTERVAL= 10 -- device_uuid.gw_sn = "21012C000038" -- device_uuid.pcs_sn= "2" -- device_uuid.batteryid = "04d0d5be-51e1-480c-9a0a-229cb6449399" device_uuid={ pcs_port = "RS485_1", bms_port = "RS485_2", -- 测试用id batteryid = "04d0d5be-51e1-480c-9a0a-229cb6449399" } -- device_uuid={ -- pcs_port = "RS485_1", -- pcs_sn = "7uaH0Lgo", -- gw_sn = "21012C000038", -- batteryid = "04d0d5be-51e1-480c-9a0a-229cb6449399" -- } cloud.aries.user = "Admin@admin.com" cloud.aries.passwd = "Password1" groupid = "1" sysPath = "/app/panasonicIfu/" dataCache={} paramMonitorCache={} globalDeviceData={} RemoteControl = {} --测试参数 ifu={ -- 逆变器 InverterMode=0,InterconnectionState=0,InverterTemp=0, -- 电池 BatteryVoltage=0,BatteryCurrent=0,BatteryTemp=0,BatteryWattage=0, RemainingCapacity=0,StateOfHealth=0,StateOfCharge=0,PowerAtCharge=0,TotalCapacity=0, AccumulatedLifetimeCharge=0,AccumulatedLifetimeDischarge=0,ChargeCapacity=0,DischargeCapacity=0, -- 电网 GridVoltage=0,GridCurrent=0,GridWattage=0,GridFrequency=0,TotalAccumulativePowerFromGrid=0,TotalAccumulativePowerToGrId=0, DailyCharge=0,DailyDischarge=0, -- 光伏 Ppv1=0,Ppv2=0,Ppv1Voltage=0,Ppv1Current=0,Ppv1Wattage=0, Ppv2Voltage=0,Ppv2Current=0,Ppv2Wattage=0, Ppv3Voltage=0,Ppv3Current=0,Ppv3Wattage=0,TotalAccumulatedSolar=0, -- 热水器 HWCurrent=0,HWWattage=0,HWVoltage=0,HWAccumulatedWattage=0,PowerFactor=0 } BatterySelectDefult= { LeadBatCapacity=105,-- A.H BattChargeVoltMax=561.6,-- V BattChargeCurrMax=25.0,-- A BattVoltUnderMin=468,-- V BattDisChgCurrMax=25.0,-- A BattSOCUnderMin=90 } local function ISO8601ToTime(datetime) log.info("ISO8601",datetime) -- local datetime = "2011-10-25T00:29:55.503-04:00" local pattern = "(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)%.*" local xyear,xmonth,xday,xhour,xminute,xseconds,xmillies,xoffset = datetime:match(pattern) local convertedTimestamp = os.time({day = xday,month = xmonth,hour = xhour,year = xyear,sec = xseconds,min = xminute})-- return convertedTimestamp end local function TimeToISO8601() return os.date("%Y-%m-%dT%X.113") end local function ifu_data_format(obj) obj.BatteryId=device_uuid.batteryid obj.Date=TimeToISO8601() --"2020-06-29T09:38:19.943Z" local str = cjson.encode(obj) return str end local function battery_add_format() obj={} obj.BatteryId=device_uuid.batteryid obj.Date=TimeToISO8601() --"2020-06-29T09:38:19.943Z" obj.IfUnitType="LNXALL_IFU" obj.IfUnitSerial=device_uuid.gw_sn obj.IfUnitSoftVer= string.gsub(io.readFile("/etc/issue"),"\n","") local str = cjson.encode(obj) return str end --- 监控参数变化,并自动做处理 -- @string name:输入模型名称 -- @string opt:操作符 ">=" ">" "<" "<=" "~=" -- @number limit:门限 -- @number func:事件触发后回调 local function ParamMonitorAddOrUpdata(name,xopt,xlimit,xfunc) paramMonitorCache[name]={opt=xopt,limit=xlimit,func=xfunc} end local function ParamMonitorDel(name) paramMonitorCache[name]={} end local function ParamMonitorCheck(obj) if not obj or type(obj) ~= "table" then return end for k,v in pairs(obj) do if paramMonitorCache[k] and paramMonitorCache[k]["opt"] and paramMonitorCache[k]["limit"] and paramMonitorCache[k]["func"] then local cmd = string.format("return %d %s %d",v,paramMonitorCache[k]["opt"],paramMonitorCache[k]["limit"]) local r,out = pcall(loadstring(cmd)) if r then if out then local func = paramMonitorCache[k]["func"] func() ParamMonitorDel(paramMonitorCache[k]) end else log.warn("ParamMonitorCheck","loadstring ", cmd) end break end end end local function ParamMonitorPrint() print(cjson.encode(paramMonitorCache)) for k,v in pairs(paramMonitorCache) do if next(v) == nil then break end log.info("ParamMonitorPrint",string.format("name %s, option %s ,limit %d", paramMonitorCache[k] , paramMonitorCache[k]["opt"] , paramMonitorCache[k]["limit"] , paramMonitorCache[k]["func"])) end end local function schedule_process(obj) if obj and obj.Signal and obj.TargetSoc and obj.PowerRate then log.warn("schedule_process",obj.Signal,obj.TargetSoc,obj.PowerRate) if obj.Signal == 0 then -- Auto 0 RemoteControl.mode = 0x2 RemoteControl.power = 0 elseif obj.Signal == 1 then -- Charge 1 RemoteControl.mode = 0x2 RemoteControl.power = obj.PowerRate == 0 and obj.PowerRate or 9500 elseif obj.Signal == 2 then -- Discharge 2 RemoteControl.mode = 0x1 RemoteControl.power = 0 elseif obj.Signal == 3 then -- Standby 3 RemoteControl={} end else log.warn("Invalid schedule message",cjson.encode(obj)) end end function on_mqtt_msg(topic, payload) -- ipc/21012C000038/VIRTUAL_1/device/333333333/data_filtered/property/vdev_data if string.find(topic,'VIRTUAL_1') then --虚拟设备上报 local payload_obj = cjson.decode(payload) if payload_obj and payload_obj.tags and type(payload_obj.tags) == "table" then ParamMonitorCheck(payload_obj.tags) table.remove(dataCache,1,#dataCache) table.insert(dataCache,payload_obj.tags) globalDeviceData = payload_obj.tags end -- local part = string.split(topic,"/") -- if device_uuid.batteryid ~= part[5] then --如果配置变化 -- device_uuid.batteryid = part[5] -- if device_uuid.gw_sn and device_uuid.pcs_sn and device_uuid.batteryid then -- io.writeFile(sysPath .. "device_uuid.json",cjson.encode(device_uuid)) -- end -- end elseif string.find(topic,"BatterySchedule")then --调度任务 schedule_process(cjson.decode(payload)) elseif string.find(topic,device_uuid.pcs_port) then -- 抓取PCS SN -- ipc/%s/RS485_1/device/+/data/service/# local part = string.split(topic,"/") if device_uuid.pcs_sn ~= part[5] then --如果配置变化 if part[2] and part[5] then device_uuid.gw_sn = part[2] device_uuid.pcs_sn = part[5] if device_uuid.gw_sn and device_uuid.pcs_sn and device_uuid.bms_sn and device_uuid.batteryid then io.writeFile(sysPath .. "device_uuid.json",cjson.encode(device_uuid)) end end end -- if string.find(topic,"data/property/post") then -- 读取PCS上报数据 -- local payload_obj = cjson.decode(payload) -- if payload_obj and payload_obj.tag_node then -- local tag_obj = cjson.decode(payload_obj.tag_node) -- if device_uuid.bms_sn and tag_obj.Inverter_Temp and tag_obj.Module_Temp and tag_obj.Radiator_Temp then --只有BMS 存在,读到PCS温度才会写入BMS -- local temp = tag_obj.Module_Temp > tag_obj.Radiator_Temp and tag_obj.Module_Temp or tag_obj.Radiator_Temp -- syslog.syslog("LOG_INFO",string.format("Inverter Status Max temp %d",temperature or 0)) -- cloud.control_dev(device_uuid.gw_sn,device_uuid.bms_sn,device_uuid.bms_port,"WriteOneReg",{RegAddr=0x0000,RegValue=temp*10}) -- end -- end -- end elseif string.find(topic,device_uuid.bms_port) then -- 抓取BMS SN -- ipc/%s/RS485_1/device/+/data/service/# local part = string.split(topic,"/") if device_uuid.bms_sn ~= part[5] then --如果配置变化 if part[2] and part[5] then device_uuid.gw_sn = part[2] device_uuid.bms_sn = part[5] if device_uuid.gw_sn and device_uuid.pcs_sn and device_uuid.bms_sn and device_uuid.batteryid then io.writeFile(sysPath .. "device_uuid.json",cjson.encode(device_uuid)) end end end end end local subscribe={} subscribe[1] = string.format("ipc/+/VIRTUAL_1/device/+/data_filtered/property/vdev_data") subscribe[2] = string.format("ipc/IFU/BatterySchedule/#") subscribe[3] = string.format("ipc/+/%s/device/+/data/property/#",device_uuid.pcs_port) subscribe[4] = string.format("ipc/+/%s/device/+/data/property/#",device_uuid.bms_port) cloud.mqtt_session("internal","localhost", 1883 , "localuser", "dywl@galaxy", subscribe, 0, on_mqtt_msg) rtos.make_dir(sysPath) -- load device_uuid local device_uuid_str = io.readFile(sysPath .. "device_uuid.json") if device_uuid_str then local obj = cjson.decode(device_uuid_str) if obj then device_uuid=obj log.info("startup","load device uuid") end end -- schedule calendar sys.taskInit(function() local last_time = 0 local last_deploy = tonumber(os.date("%d")) local obj while true do if device_uuid.gw_sn and device_uuid.pcs_sn and device_uuid.batteryid then break end sys.wait(1000) end -- load schedule local schedule_str = io.readFile(sysPath .. "schedule.json") if schedule_str then obj = cjson.decode(schedule_str) if obj then log.info("startup","load schedule") end end while true do sys.wait(1000) -- local date = os.date("*t") -- if date["day"] ~= last_deploy and date["hour"] == 0 then -- last_deploy = date["day"] if os.difftime(os.time(),last_time) > 24*3600 then last_time = os.time() print(cjson.encode(obj)) if obj and obj.ScheduleItems and #obj.ScheduleItems > 0 then --删除所有未执行调度器 local time = os.date("%H:%M %Y-%m-%d",os.time()) syslog.syslog("LOG_INFO",string.format("Current time %s",time)) local cmd = "atq | awk '{print $1}' | tr \"\\n\" \" \" | xargs atrm" log.info("Remove schedule",cmd) os.execute(cmd) -- 打印将要删除的监控任务 ParamMonitorPrint() -- 删除调度器监控参数 ParamMonitorDel("StateOfCharge") for _,item in ipairs(obj.ScheduleItems) do if item and item.Signal and item.TargetSoc and item.PowerRate then -- 测试阶段还原时区, 不使用UTC时区,不使用年月日 if item.StartDate then local topic = string.format("ipc/IFU/BatterySchedule/Start") local payload = string.gsub(cjson.encode(item),"\"","\\\"",99999) -- HH:MM YYYY-MM-DD local time = os.date("%H:%M",ISO8601ToTime(item.StartDate) - TIME_ZOOM) -- local time = os.date("%H:%M %Y-%m-%d",ISO8601ToTime(item.StartDate)) -- local time = "now" cmd = string.format("echo 'mosquitto_pub -t %s -m \"%s\"' | at %s",topic,payload,time) os.execute(cmd) syslog.syslog("LOG_INFO",string.format("Schedule Start %s, Signal %d, TargetSoc %d, PowerRate %d",time,item.Signal,item.TargetSoc,item.PowerRate)) end if item.EndDate then local topic = string.format("ipc/IFU/BatterySchedule/Stop") item.Signal = 0 local payload = string.gsub(cjson.encode(item),"\"","\\\"",99999) local time = os.date("%H:%M",ISO8601ToTime(item.EndDate) - TIME_ZOOM) -- local time = os.date("%H:%M %Y-%m-%d",ISO8601ToTime(item.EndDate)) -- local time = "now" cmd = string.format("echo 'mosquitto_pub -t %s -m \"%s\"' | at %s",topic,payload,time) os.execute(cmd) syslog.syslog("LOG_INFO",string.format("Schedule Stop",time)) -- log.info("Schedule stop",obj.Id,string.format("Time %s",time)) end end end end end end end) -- battery select sys.taskInit(function() local last_time = 0 local last_retrans = 0 while true do if device_uuid.gw_sn and device_uuid.pcs_sn and device_uuid.batteryid then break end sys.wait(1000) end -- 设置BMS index local obj={RegAddr=47504,RegValue=0xf2} cloud.control_dev(device_uuid.gw_sn,device_uuid.pcs_sn,device_uuid.pcs_port,"WriteRegsiterOfModbus",obj) -- 设置BMS protocol local obj={RegAddr=47514,RegValue=0x11e} cloud.control_dev(device_uuid.gw_sn,device_uuid.pcs_sn,device_uuid.pcs_port,"WriteRegsiterOfModbus",obj) local batt_param = BatterySelectDefult cloud.control_dev(device_uuid.gw_sn,device_uuid.pcs_sn,device_uuid.pcs_port,"BatterySelect",batt_param) while true do sys.wait(10000) if os.difftime(os.time(),last_time) > ADJ_BATT_INTERVAL then last_time = os.time() -- 根据并网离网状态判断充放电深度和电流 if globalDeviceData.InverterMode and globalDeviceData.BatteryTemp then if globalDeviceData.InverterMode == 1 then -- 并网 batt_param.BattSOCUnderMin = 80 elseif globalDeviceData.InverterMode == 2 then -- 离网 batt_param.BattSOCUnderMin = 90 end cloud.control_dev(device_uuid.gw_sn,device_uuid.pcs_sn,device_uuid.pcs_port,"BatterySelect",batt_param) end end if os.difftime(os.time(),last_retrans) > RETRANS_INTERVAL then last_retrans = os.time() if next(RemoteControl) ~= nil then cloud.control_dev(device_uuid.gw_sn,device_uuid.pcs_sn,device_uuid.pcs_port,"DisCharge_Charge_Set",RemoteControl) end end end end) --启动系统框架 sys.init(0, 0) sys.run()