sys.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. --- 模块功能:Luat协程调度框架
  2. -- @module sys
  3. -- @author openLuat
  4. -- @license MIT
  5. -- @copyright openLuat
  6. -- @release 2017.9.13
  7. require "utils"
  8. require "log"
  9. require "patch"
  10. module(..., package.seeall)
  11. -- lib脚本版本号,只要lib中的任何一个脚本做了修改,都需要更新此版本号
  12. SCRIPT_LIB_VER = "2.3.5"
  13. -- TaskID最大值
  14. local TASK_TIMER_ID_MAX = 0x1FFFFFFF
  15. -- msgId 最大值(请勿修改否则会发生msgId碰撞的危险)
  16. local MSG_TIMER_ID_MAX = 0x7FFFFFFF
  17. -- 任务定时器id
  18. local taskTimerId = 0
  19. -- 消息定时器id
  20. local msgId = TASK_TIMER_ID_MAX
  21. -- 定时器id表
  22. local timerPool = {}
  23. local taskTimerPool = {}
  24. --消息定时器参数表
  25. local para = {}
  26. --定时器是否循环表
  27. local loop = {}
  28. --lua脚本运行出错时,是否回退为本地烧写的版本
  29. local sRollBack = true
  30. -- 启动GSM协议栈。例如在充电开机未启动GSM协议栈状态下,如果用户长按键正常开机,此时调用此接口启动GSM协议栈即可
  31. -- @return 无
  32. -- @usage sys.powerOn()
  33. function powerOn()
  34. rtos.poweron(1)
  35. end
  36. --- 软件重启
  37. -- @string r 重启原因,用户自定义,一般是string类型,重启后的trace中会打印出此重启原因
  38. -- @return 无
  39. -- @usage sys.restart('程序超时软件重启')
  40. function restart(r)
  41. assert(r and r ~= "", "sys.restart cause null")
  42. if errDump and errDump.appendErr and type(errDump.appendErr) == "function" then errDump.appendErr("restart[" .. r .. "];") end
  43. rtos.restart()
  44. end
  45. --- Task任务延时函数,只能用于任务函数中
  46. -- @number ms 整数,最大等待126322567毫秒
  47. -- @return 定时结束返回nil,被其他线程唤起返回调用线程传入的参数
  48. -- @usage sys.wait(30)
  49. function wait(ms)
  50. -- 参数检测,参数不能为负值
  51. assert(ms > 0, "The wait time cannot be negative!")
  52. -- 选一个未使用的定时器ID给该任务线程
  53. if taskTimerId >= TASK_TIMER_ID_MAX then taskTimerId = 0 end
  54. taskTimerId = taskTimerId + 1
  55. local timerid = taskTimerId
  56. taskTimerPool[coroutine.running()] = timerid
  57. timerPool[timerid] = coroutine.running()
  58. -- 调用core的rtos定时器
  59. if 1 ~= rtos.timer_start(timerid, ms) then log.debug("rtos.timer_start error") return end
  60. -- 挂起调用的任务线程
  61. local message = {coroutine.yield()}
  62. if #message ~= 0 then
  63. rtos.timer_stop(timerid)
  64. taskTimerPool[coroutine.running()] = nil
  65. timerPool[timerid] = nil
  66. return unpack(message)
  67. end
  68. end
  69. --- Task任务的条件等待函数(包括事件消息和定时器消息等条件),只能用于任务函数中。
  70. -- @param id 消息ID
  71. -- @number ms 等待超时时间,单位ms,最大等待126322567毫秒
  72. -- @return result 接收到消息返回true,超时返回false
  73. -- @return data 接收到消息返回消息参数
  74. -- @usage result, data = sys.waitUntil("SIM_IND", 120000)
  75. function waitUntil(id, ms)
  76. subscribe(id, coroutine.running())
  77. local message = ms and {wait(ms)} or {coroutine.yield()}
  78. unsubscribe(id, coroutine.running())
  79. return message[1] ~= nil, unpack(message, 2, #message)
  80. end
  81. --- Task任务的条件等待函数扩展(包括事件消息和定时器消息等条件),只能用于任务函数中。
  82. -- @param id 消息ID
  83. -- @number ms 等待超时时间,单位ms,最大等待126322567毫秒
  84. -- @return message 接收到消息返回message,超时返回false
  85. -- @return data 接收到消息返回消息参数
  86. -- @usage result, data = sys.waitUntilExt("SIM_IND", 120000)
  87. function waitUntilExt(id, ms)
  88. subscribe(id, coroutine.running())
  89. local message = ms and {wait(ms)} or {coroutine.yield()}
  90. unsubscribe(id, coroutine.running())
  91. if message[1] ~= nil then return unpack(message) end
  92. return false
  93. end
  94. --- 创建一个任务线程,在模块最末行调用该函数并注册模块中的任务函数,main.lua导入该模块即可
  95. -- @param fun 任务函数名,用于resume唤醒时调用
  96. -- @param ... 任务函数fun的可变参数
  97. -- @return co 返回该任务的线程号
  98. -- @usage sys.taskInit(task1,'a','b')
  99. function taskInit(fun, ...)
  100. local co = coroutine.create(fun)
  101. coroutine.resume(co, unpack(arg))
  102. return co
  103. end
  104. --- Luat平台初始化
  105. -- @param mode 充电开机是否启动GSM协议栈,1不启动,否则启动
  106. -- @param lprfnc 用户应用脚本中定义的“低电关机处理函数”,如果有函数名,则低电时,本文件中的run接口不会执行任何动作,否则,会延时1分钟自动关机
  107. -- @return 无
  108. -- @usage sys.init(1,0)
  109. function init(mode, lprfnc)
  110. -- 用户应用脚本中必须定义PROJECT和VERSION两个全局变量,否则会死机重启,如何定义请参考各个demo中的main.lua
  111. assert(PROJECT and PROJECT ~= "" and VERSION and VERSION ~= "", "Undefine PROJECT or VERSION")
  112. collectgarbage("setpause", 80)
  113. -- 设置AT命令的虚拟串口
  114. uart.setup(uart.ATC, 0, 0, uart.PAR_NONE, uart.STOP_1)
  115. log.info("poweron reason:", rtos.poweron_reason(), PROJECT, VERSION, SCRIPT_LIB_VER, rtos.get_version())
  116. if mode == 1 then
  117. -- 充电开机
  118. if rtos.poweron_reason() == rtos.POWERON_CHARGER then
  119. -- 关闭GSM协议栈
  120. rtos.poweron(0)
  121. end
  122. end
  123. end
  124. ------------------------------------------ rtos消息回调处理部分 ------------------------------------------
  125. --[[
  126. 函数名:cmpTable
  127. 功能 :比较两个table的内容是否相同,注意:table中不能再包含table
  128. 参数 :
  129. t1:第一个table
  130. t2:第二个table
  131. 返回值:相同返回true,否则false
  132. ]]
  133. local function cmpTable(t1, t2)
  134. if not t2 then return #t1 == 0 end
  135. if #t1 == #t2 then
  136. for i = 1, #t1 do
  137. if unpack(t1, i, i) ~= unpack(t2, i, i) then
  138. return false
  139. end
  140. end
  141. return true
  142. end
  143. return false
  144. end
  145. --- 关闭定时器
  146. -- @param val 值为number时,识别为定时器ID,值为回调函数时,需要传参数
  147. -- @param ... val值为函数时,函数的可变参数
  148. -- @return 无
  149. -- @usage timerStop(1)
  150. function timerStop(val, ...)
  151. -- val 为定时器ID
  152. if type(val) == 'number' then
  153. timerPool[val], para[val], loop[val] = nil
  154. rtos.timer_stop(val)
  155. else
  156. for k, v in pairs(timerPool) do
  157. -- 回调函数相同
  158. if type(v) == 'table' and v.cb == val or v == val then
  159. -- 可变参数相同
  160. if cmpTable(arg, para[k]) then
  161. rtos.timer_stop(k)
  162. timerPool[k], para[k], loop[val] = nil
  163. break
  164. end
  165. end
  166. end
  167. end
  168. end
  169. --- 关闭同一回调函数的所有定时器
  170. -- @param fnc 定时器回调函数
  171. -- @return 无
  172. -- @usage timerStopAll(cbFnc)
  173. function timerStopAll(fnc)
  174. for k, v in pairs(timerPool) do
  175. if type(v) == "table" and v.cb == fnc or v == fnc then
  176. rtos.timer_stop(k)
  177. timerPool[k], para[k], loop[k] = nil
  178. end
  179. end
  180. end
  181. --- 开启一个定时器
  182. -- @param fnc 定时器回调函数
  183. -- @number ms 整数,最大定时126322567毫秒
  184. -- @param ... 可变参数 fnc的参数
  185. -- @return number 定时器ID,如果失败,返回nil
  186. function timerStart(fnc, ms, ...)
  187. --回调函数和时长检测
  188. assert(fnc ~= nil, "sys.timerStart(first param) is nil !")
  189. assert(ms > 0, "sys.timerStart(Second parameter) is <= zero !")
  190. -- 关闭完全相同的定时器
  191. if arg.n == 0 then
  192. timerStop(fnc)
  193. else
  194. timerStop(fnc, unpack(arg))
  195. end
  196. -- 为定时器申请ID,ID值 1-20 留给任务,20-30留给消息专用定时器
  197. while true do
  198. if msgId >= MSG_TIMER_ID_MAX then msgId = TASK_TIMER_ID_MAX end
  199. msgId = msgId + 1
  200. if timerPool[msgId] == nil then
  201. timerPool[msgId] = fnc
  202. break
  203. end
  204. end
  205. --调用底层接口启动定时器
  206. if rtos.timer_start(msgId, ms) ~= 1 then log.debug("rtos.timer_start error") return end
  207. --如果存在可变参数,在定时器参数表中保存参数
  208. if arg.n ~= 0 then
  209. para[msgId] = arg
  210. end
  211. --返回定时器id
  212. return msgId
  213. end
  214. --- 开启一个循环定时器
  215. -- @param fnc 定时器回调函数
  216. -- @number ms 整数,最大定时126322567毫秒
  217. -- @param ... 可变参数 fnc的参数
  218. -- @return number 定时器ID,如果失败,返回nil
  219. function timerLoopStart(fnc, ms, ...)
  220. local tid = timerStart(fnc, ms, unpack(arg))
  221. if tid then loop[tid] = ms end
  222. return tid
  223. end
  224. --- 判断某个定时器是否处于开启状态
  225. -- @param val 有两种形式
  226. --一种是开启定时器时返回的定时器id,此形式时不需要再传入可变参数...就能唯一标记一个定时器
  227. --另一种是开启定时器时的回调函数,此形式时必须再传入可变参数...才能唯一标记一个定时器
  228. -- @param ... 可变参数
  229. -- @return number 开启状态返回true,否则nil
  230. function timerIsActive(val, ...)
  231. if type(val) == "number" then
  232. return timerPool[val]
  233. else
  234. for k, v in pairs(timerPool) do
  235. if v == val then
  236. if cmpTable(arg, para[k]) then return true end
  237. end
  238. end
  239. end
  240. end
  241. ------------------------------------------ LUA应用消息订阅/发布接口 ------------------------------------------
  242. -- 订阅者列表
  243. local subscribers = {}
  244. --内部消息队列
  245. local messageQueue = {}
  246. --- 订阅消息
  247. -- @param id 消息id
  248. -- @param callback 消息回调处理
  249. -- @usage subscribe("NET_STATUS_IND", callback)
  250. function subscribe(id, callback)
  251. if type(id) ~= "string" or (type(callback) ~= "function" and type(callback) ~= "thread") then
  252. log.warn("warning: sys.subscribe invalid parameter", id, callback)
  253. return
  254. end
  255. if not subscribers[id] then subscribers[id] = {} end
  256. subscribers[id][callback] = true
  257. end
  258. --- 取消订阅消息
  259. -- @param id 消息id
  260. -- @param callback 消息回调处理
  261. -- @usage unsubscribe("NET_STATUS_IND", callback)
  262. function unsubscribe(id, callback)
  263. if type(id) ~= "string" or (type(callback) ~= "function" and type(callback) ~= "thread") then
  264. log.warn("warning: sys.unsubscribe invalid parameter", id, callback)
  265. return
  266. end
  267. if subscribers[id] then subscribers[id][callback] = nil end
  268. end
  269. --- 发布内部消息,存储在内部消息队列中
  270. -- @param ... 可变参数,用户自定义
  271. -- @return 无
  272. -- @usage publish("NET_STATUS_IND")
  273. function publish(...)
  274. table.insert(messageQueue, arg)
  275. end
  276. -- 分发消息
  277. local function dispatch()
  278. while true do
  279. if #messageQueue == 0 then
  280. break
  281. end
  282. local message = table.remove(messageQueue, 1)
  283. if subscribers[message[1]] then
  284. for callback, _ in pairs(subscribers[message[1]]) do
  285. if type(callback) == "function" then
  286. callback(unpack(message, 2, #message))
  287. elseif type(callback) == "thread" then
  288. coroutine.resume(callback, unpack(message))
  289. end
  290. end
  291. end
  292. end
  293. end
  294. -- rtos消息回调
  295. local handlers = {}
  296. setmetatable(handlers, {__index = function() return function() end end, })
  297. --- 注册rtos消息回调处理函数
  298. -- @number id 消息类型id
  299. -- @param handler 消息处理函数
  300. -- @return 无
  301. -- @usage rtos.on(rtos.MSG_KEYPAD, function(param) handle keypad message end)
  302. rtos.on = function(id, handler)
  303. handlers[id] = handler
  304. end
  305. ------------------------------------------ Luat 主调度框架 ------------------------------------------
  306. local function safeRun()
  307. -- 分发内部消息
  308. dispatch()
  309. -- 阻塞读取外部消息
  310. local msg, param, exparam = rtos.receive(rtos.INF_TIMEOUT)
  311. -- 判断是否为定时器消息,并且消息是否注册
  312. if msg == rtos.MSG_TIMER and timerPool[param] then
  313. if param < TASK_TIMER_ID_MAX then
  314. local taskId = timerPool[param]
  315. timerPool[param] = nil
  316. if taskTimerPool[taskId] == param then
  317. taskTimerPool[taskId] = nil
  318. coroutine.resume(taskId)
  319. end
  320. else
  321. local cb = timerPool[param]
  322. --如果不是循环定时器,从定时器id表中删除此定时器
  323. if not loop[param] then timerPool[param] = nil end
  324. if para[param] ~= nil then
  325. cb(unpack(para[param]))
  326. if not loop[param] then para[param] = nil end
  327. else
  328. cb()
  329. end
  330. --如果是循环定时器,继续启动此定时器
  331. if loop[param] then rtos.timer_start(param, loop[param]) end
  332. end
  333. --其他消息(音频消息、充电管理消息、按键消息等)
  334. elseif type(msg) == "number" then
  335. handlers[msg](param, exparam)
  336. else
  337. handlers[msg.id](msg)
  338. end
  339. end
  340. --- run()从底层获取core消息并及时处理相关消息,查询定时器并调度各注册成功的任务线程运行和挂起
  341. -- @return 无
  342. -- @usage sys.run()
  343. function run()
  344. local result, err
  345. while true do
  346. if sRollBack then
  347. safeRun()
  348. else
  349. result, err = pcall(safeRun)
  350. if not result then restart(err) end
  351. end
  352. end
  353. end
  354. --- 设置“lua脚本运行出错时,是否回退原始烧写版本”的功能开关。如果没有调用此接口设置,默认回滚
  355. -- 设置不允许回滚功能时,要同时设置一个开机时间阀值,超过这个时间,才不允许回滚
  356. -- @bool flag,“lua脚本运行出错时,是否回退原始烧写版本”的标志,true表示允许回滚,false表示不允许回滚
  357. -- @number[opt=300] secs,当配置不允许回滚时,此参数才有意义【注意:Luat_V0036_XXXX以及以后的core版本才支持此功能,此参数限制的是脚本程序无法捕获的core中lua虚拟机异常】;
  358. -- 表示一个开机时间阀值,超过这个时间,才不允许回滚;取值范围如下:
  359. -- 1到72*3600:单位为秒;表示“开机后,在此时间范围内发生异常,允许回滚”;例如300秒,表示开机运行5分钟内的错误允许回滚,5分钟后的错误不允许回滚
  360. -- @return nil
  361. -- @usage
  362. -- sys.setRollBack(true)
  363. -- sys.setRollBack(false)
  364. function setRollBack(flag,secs)
  365. sRollBack = flag
  366. secs = secs or 300
  367. if type(rtos.set_script_rollback)=="function" then
  368. rtos.set_script_rollback(1)
  369. if not flag then
  370. assert(secs>=1 and secs<=72*3600)
  371. timerStart(rtos.set_script_rollback,secs*1000,0)
  372. end
  373. end
  374. end
  375. require "clib"