Files
BR_YKC/4G/源代码/lib/sys.lua
2026-05-21 13:24:05 +08:00

527 lines
21 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
--- 模块功能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.5"
-- 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为truedata为消息ID携带的参数可能是多个参数
-- (2)、在超时时间到达之前如果没收到等待的消息ID则result为falsedata为nil
-- 2、如果不存在超时时间参数如果收到了等待的消息ID则result为truedata为消息ID携带的参数可能是多个参数
-- (1)、如果收到了等待的消息ID则result为truedata为消息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
--- 同上,但不返回等待结果
function sys.waitUntilMsg(id)
local co = sys.check_task()
sys.subscribe(id, co)
local message = {coroutine.yield()}
sys.unsubscribe(id, co)
return 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
-- 为定时器申请IDID值 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