| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- --- 模块功能:Luat协程调度框架
- -- @module sys
- -- @author openLuat
- -- @license MIT
- -- @copyright openLuat
- -- @release 2017.9.13
- require "utils"
- require "log"
- require "patch"
- module(..., package.seeall)
- -- lib脚本版本号,只要lib中的任何一个脚本做了修改,都需要更新此版本号
- SCRIPT_LIB_VER = "2.4.4"
- -- TaskID最大值
- local TASK_TIMER_ID_MAX = 0x1FFFFFFF
- -- msgId 最大值(请勿修改否则会发生msgId碰撞的危险)
- local MSG_TIMER_ID_MAX = 0x7FFFFFFF
- -- 任务定时器id
- local taskTimerId = 0
- -- 消息定时器id
- local msgId = TASK_TIMER_ID_MAX
- -- 定时器id表
- local timerPool = {}
- local taskTimerPool = {}
- --消息定时器参数表
- local para = {}
- --定时器是否循环表
- local loop = {}
- -- 启动GSM协议栈。例如在充电开机未启动GSM协议栈状态下,如果用户长按键正常开机,此时调用此接口启动GSM协议栈即可
- -- @return 无
- -- @usage sys.powerOn()
- function powerOn()
- rtos.poweron(1)
- end
- --- 软件重启
- -- @string r 重启原因,用户自定义,一般是string类型,重启后的trace中会打印出此重启原因
- -- @return 无
- -- @usage sys.restart('程序超时软件重启')
- function restart(r)
- assert(r and r ~= "", "sys.restart cause null")
- if errDump and errDump.appendErr and type(errDump.appendErr) == "function" then errDump.appendErr("restart[" .. r .. "];") end
- log.warn("sys.restart", r)
- rtos.restart()
- end
- --- task任务延时函数
- -- 只能直接或者间接的被task任务主函数调用,如果定时器创建成功,则本task会挂起
- -- @number ms 延时时间,单位毫秒,最小1,最大0x7FFFFFFF
- -- 实际上支持的最小超时时间是5毫秒,小于5毫秒的时间都会被转化为5毫秒
- -- @return result,分为如下三种情况:
- -- 1、如果定时器创建失败,本task不会被挂起,直接返回nil
- -- 2、如果定时器创建成功,本task被挂起,超时时间到达后,会激活本task,返回nil
- -- 3、如果定时器创建成功,本task被挂起,在超时时间到达之前,其他业务逻辑主动激活本task,
- -- 返回激活时携带的可变参数(如果不是故意为之,可能是写bug了)
- -- @usage
- -- task延时5秒:
- -- sys.taskInit(function()
- -- sys.wait(5000)
- -- end)
- function wait(ms)
- -- 参数检测,参数不能为负值
- assert(ms > 0, "The wait time cannot be negative!")
- --4G底层不支持小于5ms的定时器
- if ms < 5 then ms = 5 end
- -- 选一个未使用的定时器ID给该任务线程
- if taskTimerId >= TASK_TIMER_ID_MAX then taskTimerId = 0 end
- taskTimerId = taskTimerId + 1
- local timerid = taskTimerId
- taskTimerPool[coroutine.running()] = timerid
- timerPool[timerid] = coroutine.running()
- -- 调用core的rtos定时器
- if 1 ~= rtos.timer_start(timerid, ms) then log.debug("rtos.timer_start error") return end
- -- 挂起调用的任务线程
- local message = {coroutine.yield()}
- if #message ~= 0 then
- rtos.timer_stop(timerid)
- taskTimerPool[coroutine.running()] = nil
- timerPool[timerid] = nil
- return unpack(message)
- end
- end
- --- task任务条件等待函数(支持事件消息和定时器消息)
- -- 只能直接或者间接的被task任务主函数调用,调用本接口的task会挂起
- -- @string id 消息ID,建议使用string类型
- -- @number[opt=nil] ms 延时时间,单位毫秒,最小1,最大0x7FFFFFFF
- -- 实际上支持的最小超时时间是5毫秒,小于5毫秒的时间都会被转化为5毫秒
- -- @return result,data,分为如下三种情况:
- -- 1、如果存在超时时间参数:
- -- (1)、在超时时间到达之前,如果收到了等待的消息ID,则result为true,data为消息ID携带的参数(可能是多个参数)
- -- (2)、在超时时间到达之前,如果没收到等待的消息ID,则result为false,data为nil
- -- 2、如果不存在超时时间参数:如果收到了等待的消息ID,则result为true,data为消息ID携带的参数(可能是多个参数)
- -- (1)、如果收到了等待的消息ID,则result为true,data为消息ID携带的参数(可能是多个参数)
- -- (2)、如果没收到等待的消息ID,则task一直挂起
- -- 3、还存在一种特殊情况,本task挂起时,可能被task的外部应用逻辑给主动激活(如果不是故意为之,可能是写bug了)
- -- @usage
- -- task延时120秒或者收到"SIM_IND"消息:
- -- sys.taskInit(function()
- -- local result, data = sys.waitUntil("SIM_IND",120000)
- -- end)
- function waitUntil(id, ms)
- subscribe(id, coroutine.running())
- local message = ms and {wait(ms)} or {coroutine.yield()}
- unsubscribe(id, coroutine.running())
- return message[1] ~= nil, unpack(message, 2, #message)
- end
- --- Task任务的条件等待函数扩展(包括事件消息和定时器消息等条件),只能用于任务函数中。
- -- @param id 消息ID
- -- @number ms 等待超时时间,单位ms,最大等待126322567毫秒
- -- @return message 接收到消息返回message,超时返回false
- -- @return data 接收到消息返回消息参数
- -- @usage result, data = sys.waitUntilExt("SIM_IND", 120000)
- function waitUntilExt(id, ms)
- subscribe(id, coroutine.running())
- local message = ms and {wait(ms)} or {coroutine.yield()}
- unsubscribe(id, coroutine.running())
- if message[1] ~= nil then return unpack(message) end
- return false
- end
- --- 创建一个任务并且运行该任务
- -- @param fun 任务主函数,激活task时使用
- -- @param ... 任务主函数fun的可变参数
- -- @return co 返回该任务的线程ID
- -- @usage sys.taskInit(task1,'a','b')
- function taskInit(fun, ...)
- local co = coroutine.create(fun)
- coroutine.resume(co, ...)
- return co
- end
- --- Luat平台初始化
- -- @param mode 充电开机是否启动GSM协议栈,1不启动,否则启动
- -- @param lprfnc 用户应用脚本中定义的“低电关机处理函数”,如果有函数名,则低电时,本文件中的run接口不会执行任何动作,否则,会延时1分钟自动关机
- -- @return 无
- -- @usage sys.init(1,0)
- function init(mode, lprfnc)
- -- 用户应用脚本中必须定义PROJECT和VERSION两个全局变量,否则会死机重启,如何定义请参考各个demo中的main.lua
- assert(PROJECT and PROJECT ~= "" and VERSION and VERSION ~= "", "Undefine PROJECT or VERSION")
- collectgarbage("setpause", 80)
- -- 设置AT命令的虚拟串口
- uart.setup(uart.ATC, 0, 0, uart.PAR_NONE, uart.STOP_1)
- log.info("poweron reason:", rtos.poweron_reason(), PROJECT, VERSION, SCRIPT_LIB_VER, rtos.get_version())
- pcall(rtos.set_lua_info,"\r\n"..rtos.get_version().."\r\n"..(_G.PROJECT or "NO PROJECT").."\r\n"..(_G.VERSION or "NO VERSION"))
- if type(rtos.get_build_time)=="function" then log.info("core build time", rtos.get_build_time()) end
- if mode == 1 then
- -- 充电开机
- if rtos.poweron_reason() == rtos.POWERON_CHARGER then
- -- 关闭GSM协议栈
- rtos.poweron(0)
- end
- end
- end
- ------------------------------------------ rtos消息回调处理部分 ------------------------------------------
- --[[
- 函数名:cmpTable
- 功能 :比较两个table的内容是否相同,注意:table中不能再包含table
- 参数 :
- t1:第一个table
- t2:第二个table
- 返回值:相同返回true,否则false
- ]]
- local function cmpTable(t1, t2)
- if not t2 then return #t1 == 0 end
- if #t1 == #t2 then
- for i = 1, #t1 do
- if unpack(t1, i, i) ~= unpack(t2, i, i) then
- return false
- end
- end
- return true
- end
- return false
- end
- --- 关闭sys.timerStart和sys.timerLoopStart创建的定时器
- -- 有两种方式可以唯一标识一个定时器:
- -- 1、定时器ID
- -- 2、定时器回调函数和可变参数
- -- @param val 有两种形式:
- -- 1、为number类型时,表示定时器ID
- -- 2、为function类型时,表示定时器回调函数
- -- @param ... 可变参数,当val为定时器回调函数时,此可变参数才有意义,表示定时器回调函数的可变回调参数
- -- @return nil
- -- @usage
- -- 通过定时器ID关闭一个定时器:
- -- local timerId = sys.timerStart(publicTimerCbFnc,8000,"second")
- -- sys.timerStop(timerId)
- -- 通过定时器回调函数和可变参数关闭一个定时器:
- -- sys.timerStart(publicTimerCbFnc,8000,"first")
- -- sys.timerStop(publicTimerCbFnc,"first")
- function timerStop(val, ...)
- -- val 为定时器ID
- local arg={ ... }
- if type(val) == 'number' then
- timerPool[val], para[val], loop[val] = nil
- rtos.timer_stop(val)
- else
- for k, v in pairs(timerPool) do
- -- 回调函数相同
- if type(v) == 'table' and v.cb == val or v == val then
- -- 可变参数相同
- if cmpTable(arg, para[k]) then
- rtos.timer_stop(k)
- timerPool[k], para[k], loop[val] = nil
- break
- end
- end
- end
- end
- end
- --- 关闭sys.timerStart和sys.timerLoopStart创建的某个回调函数的所有定时器
- -- @function fnc 定时器回调函数
- -- @return nil
- -- @usage
- -- 关闭回调函数为publicTimerCbFnc的所有定时器
- -- local function publicTimerCbFnc(tag)
- -- log.info("publicTimerCbFnc",tag)
- -- end
- --
- -- sys.timerStart(publicTimerCbFnc,8000,"first")
- -- sys.timerStart(publicTimerCbFnc,8000,"second")
- -- sys.timerStart(publicTimerCbFnc,8000,"third")
- -- sys.timerStopAll(publicTimerCbFnc)
- function timerStopAll(fnc)
- for k, v in pairs(timerPool) do
- if type(v) == "table" and v.cb == fnc or v == fnc then
- rtos.timer_stop(k)
- timerPool[k], para[k], loop[k] = nil
- end
- end
- end
- --- 创建并且启动一个单次定时器
- -- 有两种方式可以唯一标识一个定时器:
- -- 1、定时器ID
- -- 2、定时器回调函数和可变参数
- -- @param fnc 定时器回调函数,必须存在,不允许为nil
- -- 当定时器超时时间到达时,回调函数的调用形式为fnc(...),其中...为回调参数
- -- @number ms 定时器超时时间,单位毫秒,最小1,最大0x7FFFFFFF
- -- 实际上支持的最小超时时间是5毫秒,小于5毫秒的时间都会被转化为5毫秒
- -- @param ... 可变参数,回调函数fnc的回调参数
- -- @return number timerId,创建成功返回定时器ID;创建失败返回nil
- -- @usage
- -- 创建一个5秒的单次定时器,回调函数打印"timerCb",没有可变参数:
- -- sys.timerStart(function() log.info("timerCb") end, 5000)
- -- 创建一个5秒的单次定时器,回调函数打印"timerCb"和"test",可变参数为"test":
- -- sys.timerStart(function(tag) log.info("timerCb",tag) end, 5000, "test")
- function timerStart(fnc, ms, ...)
- --回调函数和时长检测
- local arg={ ... }
- local argcnt=0
- for i, v in pairs(arg) do
- argcnt = argcnt+1
- end
- assert(fnc ~= nil, "sys.timerStart(first param) is nil !")
- assert(ms > 0, "sys.timerStart(Second parameter) is <= zero !")
- --4G底层不支持小于5ms的定时器
- if ms < 5 then ms = 5 end
- -- 关闭完全相同的定时器
- if argcnt == 0 then
- timerStop(fnc)
- else
- timerStop(fnc, ...)
- end
- -- 为定时器申请ID,ID值 1-0X1FFFFFFF 留给任务,0X1FFFFFFF-0x7FFFFFFF留给消息专用定时器
- while true do
- if msgId >= MSG_TIMER_ID_MAX then msgId = TASK_TIMER_ID_MAX end
- msgId = msgId + 1
- if timerPool[msgId] == nil then
- timerPool[msgId] = fnc
- break
- end
- end
- --调用底层接口启动定时器
- if rtos.timer_start(msgId, ms) ~= 1 then log.debug("rtos.timer_start error") return end
- --如果存在可变参数,在定时器参数表中保存参数
- if argcnt ~= 0 then
- para[msgId] = arg
- end
- --返回定时器id
- return msgId
- end
- --- 创建并且启动一个循环定时器
- -- 有两种方式可以唯一标识一个定时器:
- -- 1、定时器ID
- -- 2、定时器回调函数和可变参数
- -- @param fnc 定时器回调函数,必须存在,不允许为nil
- -- 当定时器超时时间到达时,回调函数的调用形式为fnc(...),其中...为回调参数
- -- @number ms 定时器超时时间,单位毫秒,最小1,最大0x7FFFFFFF
- -- 实际上支持的最小超时时间是5毫秒,小于5毫秒的时间都会被转化为5毫秒
- -- @param ... 可变参数,回调函数fnc的回调参数
- -- @return number timerId,创建成功返回定时器ID;创建失败返回nil
- -- @usage
- -- 创建一个5秒的循环定时器,回调函数打印"timerCb",没有可变参数:
- -- sys.timerLoopStart(function() log.info("timerCb") end, 5000)
- -- 创建一个5秒的循环定时器,回调函数打印"timerCb"和"test",可变参数为"test":
- -- sys.timerLoopStart(function(tag) log.info("timerCb",tag) end, 5000, "test")
- function timerLoopStart(fnc, ms, ...)
- local tid = timerStart(fnc, ms, ...)
- if tid then loop[tid] = (ms<5 and 5 or ms) end
- return tid
- end
- --- 判断“通过timerStart或者timerLoopStart创建的定时器”是否处于激活状态
- -- @param val 定时器标识,有两种表示形式
- -- 1、number类型,通过timerStart或者timerLoopStart创建定时器时返回的定时器ID,此情况下,不需要传入回调参数...就能唯一标识一个定时器
- -- 2、function类型,通过timerStart或者timerLoopStart创建定时器时的回调函数,此情况下,如果存在回调参数,需要传入回调参数...才能唯一标识一个定时器
- -- @param ... 回调参数,和“通过timerStart或者timerLoopStart创建定时器”的回调参数保持一致
- -- @return status,定时器激活状态;根据val的表示形式,有不同的返回值:
- -- 1、val为number类型时:如果处于激活状态,则返回function类型的定时器回调函数;否则返回nil
- -- 2、val为function类型时:如果处于激活状态,则返回bool类型的true;否则返回nil
- -- @usage
- -- 定时器ID形式标识定时器的使用参考:
- -- local timerId1 = sys.timerStart(function() end,5000)
- --
- -- sys.taskInit(function()
- -- sys.wait(3000)
- -- log.info("after 3 senonds, timerId1 isActive?",sys.timerIsActive(timerId1))
- --
- -- sys.wait(3000)
- -- log.info("after 6 senonds, timerId1 isActive?",sys.timerIsActive(timerId1))
- -- end)
- --
- --
- -- 回调函数和回调参数标识定时器的使用参考:
- -- local function timerCbFnc2(tag)
- -- log.info("timerCbFnc2",tag)
- -- end
- --
- -- sys.timerStart(timerCbFnc2,5000,"test")
- --
- -- sys.taskInit(function()
- -- sys.wait(3000)
- -- log.info("after 3 senonds, timerCbFnc2 test isActive?",sys.timerIsActive(timerCbFnc2,"test"))
- --
- -- sys.wait(3000)
- -- log.info("after 6 senonds, timerCbFnc2 test isActive?",sys.timerIsActive(timerCbFnc2,"test"))
- -- end)
- function timerIsActive(val, ...)
- local arg={ ... }
- if type(val) == "number" then
- return timerPool[val]
- else
- for k, v in pairs(timerPool) do
- if v == val then
- if cmpTable(arg, para[k]) then return true end
- end
- end
- end
- end
- ------------------------------------------ LUA应用消息订阅/发布接口 ------------------------------------------
- -- 订阅者列表
- local subscribers = {}
- --内部消息队列
- local messageQueue = {}
- --- 订阅消息
- -- @param id 消息id
- -- @param callback 消息回调处理
- -- @usage subscribe("NET_STATUS_IND", callback)
- function subscribe(id, callback)
- if type(id) ~= "string" or (type(callback) ~= "function" and type(callback) ~= "thread") then
- log.warn("warning: sys.subscribe invalid parameter", id, callback)
- return
- end
- if not subscribers[id] then subscribers[id] = {count = 0} end
- if not subscribers[id][callback] then
- subscribers[id].count = subscribers[id].count + 1
- subscribers[id][callback] = true
- end
- end
- --- 取消订阅消息
- -- @param id 消息id
- -- @param callback 消息回调处理
- -- @usage unsubscribe("NET_STATUS_IND", callback)
- function unsubscribe(id, callback)
- if type(id) ~= "string" or (type(callback) ~= "function" and type(callback) ~= "thread") then
- log.warn("warning: sys.unsubscribe invalid parameter", id, callback)
- return
- end
- -- 取消订阅时将对应取消的函数赋值为false,不能直接赋值为nil,否则可能触发lua invalid key to 'next'异常
- if subscribers[id] then
- if subscribers[id][callback] then
- subscribers[id].count = subscribers[id].count - 1
- subscribers[id][callback] = false
- end
- end
- end
- --- 发布内部消息,存储在内部消息队列中
- -- @param ... 可变参数,用户自定义
- -- @return 无
- -- @usage publish("NET_STATUS_IND")
- function publish(...)
- local arg = { ... }
- table.insert(messageQueue, arg)
- end
- -- 分发消息
- local function dispatch()
- while true do
- if #messageQueue == 0 then
- -- 当在同一个task内sys.waitUntil()不同的消息,并且没有任何消息publish时,会造成内存泄漏。
- -- 例如:sys.waitUntil("1", 500)、sys.waitUntil("2", 500)、...、sys.waitUntil("n", 500)
- for k, v in pairs(subscribers) do
- if v.count == 0 then subscribers[k] = nil end
- end
- break
- end
- local message = table.remove(messageQueue, 1)
- if subscribers[message[1]] then
- for callback, flag in pairs(subscribers[message[1]]) do
- if flag then
- if type(callback) == "function" then
- callback(unpack(message, 2, #message))
- elseif type(callback) == "thread" then
- coroutine.resume(callback, unpack(message))
- end
- end
- end
- -- 当某个subscribe的消息的回调取消订阅时,在这里将它赋值为nil,回收内存
- if subscribers[message[1]] then
- for callback, flag in pairs(subscribers[message[1]]) do
- if not flag then
- subscribers[message[1]][callback] = nil
- end
- end
- --当所有subscribe消息的回调都取消订阅时,在这里清空对应消息的表,回收内存
- if subscribers[message[1]].count == 0 then
- subscribers[message[1]] = nil
- end
- end
- end
- end
- end
- -- rtos消息回调
- local handlers = {}
- setmetatable(handlers, {__index = function() return function() end end, })
- --- 注册rtos消息回调处理函数
- -- @number id 消息类型id
- -- @param handler 消息处理函数
- -- @return 无
- -- @usage rtos.on(rtos.MSG_KEYPAD, function(param) handle keypad message end)
- rtos.on = function(id, handler)
- handlers[id] = handler
- end
- ------------------------------------------ Luat 主调度框架 ------------------------------------------
- --- run()从底层获取core消息并及时处理相关消息,查询定时器并调度各注册成功的任务线程运行和挂起
- -- @return 无
- -- @usage sys.run()
- function run()
- while true do
- -- 分发内部消息
- dispatch()
- -- 阻塞读取外部消息
- local msg, param = rtos.receive(rtos.INF_TIMEOUT)
- -- 判断是否为定时器消息,并且消息是否注册
- if msg == rtos.MSG_TIMER and timerPool[param] then
- if param <= TASK_TIMER_ID_MAX then
- local taskId = timerPool[param]
- timerPool[param] = nil
- if taskTimerPool[taskId] == param then
- taskTimerPool[taskId] = nil
- coroutine.resume(taskId)
- end
- else
- local cb = timerPool[param]
- --如果不是循环定时器,从定时器id表中删除此定时器
- if not loop[param] then timerPool[param] = nil end
- if para[param] ~= nil then
- cb(unpack(para[param]))
- if not loop[param] then para[param] = nil end
- else
- cb()
- end
- --如果是循环定时器,继续启动此定时器
- if loop[param] then rtos.timer_start(param, loop[param]) end
- end
- --其他消息(音频消息、充电管理消息、按键消息等)
- elseif type(msg) == "number" then
- handlers[msg](param)
- else
- handlers[msg.id](msg)
- end
- end
- end
- require "clib"
- if type(rtos.openSoftDog)=="function" then
- rtos.openSoftDog(60000)
- sys.timerLoopStart(rtos.eatSoftDog,20000)
- end
|