--- 模块功能:参数管理 -- @module nvm -- @author openLuat -- @license MIT -- @copyright openLuat -- @release 2017.11.9 require "log" module(..., package.seeall) --实时参数配置存储在paraname文件中 --默认参数配置存储在configname文件中 --para:实时参数表 --config:默认参数表 paraname, paranamebak = "/nvm_para.lua", "/nvm_para_bak.lua" para = {} local libdftconfig, configname, cconfigname, econfigname local sBurnSave --[[ 函数名:serialize 功能 :根据不同的数据类型,按照不同的格式,写格式化后的数据到文件中 参数 : pout:文件句柄 o:数据 返回值:无 ]] local function serialize(pout, o) if type(o) == "number" then --number类型,直接写原始数据 pout:write(o) elseif type(o) == "string" then --string类型,原始数据左右各加上双引号写入 pout:write(string.format("%q", o)) elseif type(o) == "boolean" then --boolean类型,转化为string写入 pout:write(tostring(o)) elseif type(o) == "table" then --table类型,加换行,大括号,中括号,双引号写入 pout:write("{\n") for k, v in pairs(o) do -- if type(k) == "number" then -- pout:write(" [" .. k .. "] = ") -- elseif type(k) == "string" then -- pout:write(" [\"" .. k .."\"] = ") -- else -- error("cannot serialize table key " .. type(o)) -- end pout:write(" [") serialize(pout, k) pout:write("] = ") serialize(pout, v) pout:write(",\n") end pout:write("}\n") else error("cannot serialize a " .. type(o)) end end --[[ 函数名:upd 功能 :更新实时参数表 参数 : overide:是否用默认参数强制更新实时参数 返回值:无 ]] function upd(overide) for k, v in pairs(libdftconfig) do if k ~= "_M" and k ~= "_NAME" and k ~= "_PACKAGE" then if overide or para[k] == nil then para[k] = v end end end end local function safePcall(file) local oldPath = package.path package.path = "/?.lua;".."/?.luae;".."/#lua_user#/?.lua;".."/#lua_user#/?.luae;"..package.path local result, para = pcall(require, file) package.path = oldPath return result, para end --[[ 函数名:load 功能 :初始化参数 参数 :无 返回值:如果参数文件异常,会恢复出厂设置,此时返回false;其余情况返回nil ]] local function load() local f, fBak, fExist, fBakExist f = io.open(paraname, "rb") fBak = io.open(paranamebak, "rb") if f then fExist = f:read("*a") ~= "" f:close() end if fBak then fBakExist = fBak:read("*a") ~= "" fBak:close() end print("load fExist fBakExist", fExist, fBakExist) local fResult, fBakResult if fExist then if sBurnSave then fResult, para = safePcall(paraname:match("/#lua_user#/(.+)%.lua")) else fResult, para = safePcall(paraname:match("/(.+)%.lua")) end end print("load fResult,type(para)", fResult,type(para)) local fError,fBakError if fResult then if type(para)=="table" then os.remove(paranamebak) upd() return else fError = true end end if fBakExist then if sBurnSave then fBakResult, para = safePcall(paranamebak:match("/#lua_user#/(.+)%.lua")) else fBakResult, para = safePcall(paranamebak:match("/(.+)%.lua")) end end print("load fBakResult,type(para)", fBakResult,type(para)) if fBakResult then if type(para)=="table" then os.remove(paraname) upd() return else fBakError = true end else para = {} restore() end --两种参数文件都不存在,不算异常,以下算是异常情况: --1、参数文件存在,备份参数文件不存在 -- (1)、参数文件require异常 --2、参数文件不存在,备份参数文件存在 -- (1)、备份参数文件require异常 --3、参数文件存在,备份参数文件存在 -- (1)、参数文件和备份参数文件require都异常 if fExist and not fBakExist then if not fResult or fError then return false end end if not fExist and fBakExist then if not fBakResult or fBakError then return false end end if fExist and fBakExist then if (not fResult or fError) and (not fBakResult or fBakError) then return false end end end --[[ 函数名:save 功能 :保存参数文件 参数 : s:是否真正保存,true保存,false或者nil不保存 返回值:无 ]] local function save(s) if not s then return end local f = {} f.write = function(self, s)table.insert(self, s) end f:write("module(...)\n") for k, v in pairs(para) do if k ~= "_M" and k ~= "_NAME" and k ~= "_PACKAGE" then f:write(k .. " = ") serialize(f, v) f:write("\n") end end local fparabak = io.open(paranamebak, 'wb') fparabak:write(table.concat(f)) fparabak:close() os.remove(paraname) os.rename(paranamebak, paraname) end --- 初始化参数存储管理模块 -- @string defaultCfgFile 默认参数文件名 -- @bool burnSave 本地烧录时是否保留已有参数,true为保留,false或者nil为清除 -- 注意:在同一个项目,不同版本中,此参数必须保持前后版本一致 -- @return boolean result,如果初始化异常,会恢复出厂设置,此时返回false;其余情况返回nil -- @usage -- 初始化参数存储管理模块,默认参数文件名为config.lua,本地烧录时清除已有的参数: -- nvm.init("config.lua") -- 初始化参数存储管理模块,默认参数文件名为config.lua,本地烧录时保留已有的参数: -- nvm.init("config.lua",true) function init(defaultCfgFile,burnSave) local f f, libdftconfig = safePcall(defaultCfgFile:match("(.+)%.lua")) configname, cconfigname, econfigname = "/lua/" .. defaultCfgFile, "/lua/" .. defaultCfgFile .. "c", "/lua/" .. defaultCfgFile .. "e" sBurnSave = burnSave if burnSave then rtos.make_dir("/#lua_user#") paraname, paranamebak = "/#lua_user#/nvm_para.lua", "/#lua_user#/nvm_para_bak.lua" end --初始化配置文件,从文件中把参数读取到内存中 return load() end --- 设置某个参数的值 -- @string k 参数名 -- @param v 参数的新值,仅支持number、string、boolean、table类型 -- @param r 设置原因,如果传入了非nil的有效参数,并且v值和旧值相比发生了改变, -- 会产生一个PARA_CHANGED_IND内部消息,携带 k,v,r 3个参数 -- @param s 是否立即写入到文件系统中,false不写入,其余的都写入 -- @return bool或者nil,成功返回true,失败返回nil -- @usage -- 参数name赋值为Luat,立即写入文件系统: -- nvm.set("name","Luat") -- -- 参数age赋值为12,立即写入文件系统: -- 如果旧值不是12,会产生一个PARA_CHANGED_IND消息,携带 "age",12,"SVR" 3个参数: -- nvm.set("age",12,"SVR") -- -- 参数class赋值为Class2,不写入文件系统: -- nvm.set("class","Class2",nil,false) -- -- 参数score赋值为{chinese=100,math=99,english=98},立即写入文件系统: -- nvm.set("score",{chinese=100,math=99,english=98}) -- -- 连续写入4个参数,前3个不保存到文件系统中,写第4个时,一次性全部保存到文件系统中: -- nvm.set("para1",1,nil,false) -- nvm.set("para2",2,nil,false) -- nvm.set("para3",3,nil,false) -- nvm.set("para4",4) function set(k, v, r, s) local bchg = true if type(v) ~= "table" then bchg = (para[k] ~= v) end log.info("nvm.set", bchg, k, v, r, s) if bchg then para[k] = v save(s or s == nil) if r then sys.publish("PARA_CHANGED_IND", k, v, r) end end return true end --- 设置某个table类型参数的某一个索引的值 -- @string k table类型的参数名 -- @param kk table类型参数的某一个索引名 -- @param v table类型参数的某一个索引的新值 -- @param r 设置原因,如果传入了非nil的有效参数,并且v值和旧值相比发生了改变,会产生一个TPARA_CHANGED_IND消息,携带k,kk,v,r4个参数 -- @param s 是否立即写入到文件系统中,false不写入,其余的都写入 -- @return bool或者nil,成功返回true,失败返回nil -- @usage nvm.sett("score","chinese",100),参数score["chinese"]赋值为100,立即写入文件系统 -- @usage nvm.sett("score","chinese",100,"SVR"),参数score["chinese"]赋值为100,立即写入文件系统, -- 如果旧值不是100,会产生一个TPARA_CHANGED_IND消息,携带 "score","chinese",100,"SVR" 4个参数 -- @usage nvm.sett("score","chinese",100,nil,false),参数class赋值为Class2,不写入文件系统 function sett(k, kk, v, r, s) local bchg = true if type(v) ~= "table" then bchg = (para[k][kk] ~= v) end if bchg then para[k][kk] = v save(s or s == nil) if r then sys.publish("TPARA_CHANGED_IND", k, kk, v, r) end end return true end --- 所有参数立即写入文件系统 -- @return nil -- @usage nvm.flush() function flush() save(true) end --- 读取某个参数的值 -- @string k 参数名 -- @return 参数值 -- @usage -- 读取参数名为name的参数值: -- nameValue = nvm.get("name") function get(k) return para[k] end --- 读取某个table类型参数的键名对应的值 -- @string k table类型的参数名 -- @param kk table类型参数的键名 -- @usage -- 有一个table参数为score,数据如下: -- score = {chinese=100, math=100, english=95} -- 读取score中chinese对应的值: -- nvm.gett("score","chinese") function gett(k, kk) return para[k][kk] end --- 参数恢复出厂设置 -- @return nil -- @usage nvm.restore() function restore() os.remove(paraname) os.remove(paranamebak) local fpara, fconfig = io.open(paraname, "wb"), io.open(configname, "rb") if not fconfig then fconfig = io.open(cconfigname, "rb") end if not fconfig then fconfig = io.open(econfigname, "rb") end fpara:write(fconfig:read("*a")) fpara:close() fconfig:close() upd(true) end --- 请求删除参数文件. -- 此接口一般用在远程升级时,需要用新的config.lua覆盖原来的参数文件的场景,在此场景下,远程升级包下载成功后,在确定要重启前调用此接口 -- 下次开机执行nvm.init("config.lua")时,会用新的config.lua文件自动覆盖参数文件;以后再开机就不会自动覆盖了 -- 也就是说"nvm.remove()->重启->nvm.init("config.lua")"是一个仅执行一次的完整操作 -- @return nil -- @usage nvm.remove() function remove() os.remove(paraname) os.remove(paranamebak) end