sys.lua 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. --- 模块功能:Luat协程调度框架
  2. --[[
  3. @module sys
  4. @summary LuaTask核心逻辑
  5. @version 1.0
  6. @date 2018.01.01
  7. @author 稀饭/wendal/晨旭
  8. @usage
  9. -- sys一般会内嵌到固件内, 不需要手工添加到脚本列表,除非你正在修改sys.lua
  10. -- 本文件修改后, 需要调用 update_lib_inline 对 vfs 中的C文件进行更新
  11. _G.sys = require("sys")
  12. sys.taskInit(function()
  13. sys.wait(1000)
  14. log.info("sys", "say hi")
  15. end)
  16. sys.run()
  17. ]]
  18. local sys = {}
  19. local table = _G.table
  20. local unpack = table.unpack
  21. local rtos = _G.rtos
  22. local coroutine = _G.coroutine
  23. local log = _G.log
  24. -- lib脚本版本号,只要lib中的任何一个脚本做了修改,都需要更新此版本号
  25. SCRIPT_LIB_VER = "2.3.2"
  26. -- 任务定时器id最大值
  27. local TASK_TIMER_ID_MAX = 0x1FFFFF
  28. -- 消息定时器id最大值(请勿修改否则会发生msgId碰撞的危险)
  29. local MSG_TIMER_ID_MAX = 0x7FFFFF
  30. -- 任务定时器id(通过sys.wait或者sys.waitUntil接口创建定时器时动态分配的定时器id)
  31. -- 定时器id的取值范围为0到TASK_TIMER_ID_MAX - 1
  32. local taskTimerId = 0
  33. -- 消息定时器id(通过sys.timerStart或者sys.timerLoopStart或者sys.waitMsg接口创建定时器时动态分配的定时器id)
  34. -- 定时器id的取值范围为TASK_TIMER_ID_MAX +1 到 MSG_TIMER_ID_MAX
  35. local msgId = TASK_TIMER_ID_MAX
  36. -- 定时器处理表
  37. -- 表中记录了两种类型的定时器
  38. -- 第一种定时器记录的是定时器id和对应的回调函数
  39. -- 第二种定时器记录的是定时器id和对应的task对象
  40. -- 数据结构如下:
  41. -- local timerPool =
  42. -- {
  43. -- -- id1表示:通过sys.timerStart或者sys.timerLoopStart或者sys.waitMsg接口创建定时器时,sys.lua内部自动分配的定时器id
  44. -- -- func1表示:定时器对应的回调函数,例如:sys.timerStart(blink_on, 5000)中的回调函数blink_on
  45. -- [id1] = func1,
  46. -- -- id2表示:通过sys.wait或者sys.waitUntil接口创建定时器时动态分配的定时器id
  47. -- -- co2表示:创建定时器时,当前正常运行的协程(task)对象,例如:schedulig.lua中sys.wait所处的task对象
  48. -- [id2] = co2,
  49. --
  50. -- ...
  51. -- }
  52. local timerPool = {}
  53. -- 定时器回调函数参数表
  54. -- 此处的定时器指的是timerPool中记录的第一种定时器
  55. -- 数据结构如下:
  56. -- local para =
  57. -- {
  58. -- -- id1表示:通过sys.timerStart或者sys.timerLoopStart或者sys.waitMsg接口创建定时器时,sys.lua内部自动分配的定时器id
  59. -- -- {...}表示:定时器对应的回调函数参数,例如:sys.timerStart(blink_on, 5000, "red", 1)中的{"red", 1}
  60. -- [id1] = {...},
  61. -- ...
  62. -- }
  63. local para = {}
  64. --定时器是否循环表
  65. --local loop = {}
  66. --lua脚本运行出错时,是否回退为本地烧写的版本
  67. --local sRollBack = true
  68. _G.COROUTINE_ERROR_ROLL_BACK = true
  69. _G.COROUTINE_ERROR_RESTART = true
  70. -- 对coroutine.resume加一个修饰器用于捕获协程错误
  71. --local rawcoresume = coroutine.resume
  72. local function wrapper(co,...)
  73. local arg = {...}
  74. -- arg[1]为false,表示协程执行出错
  75. -- 此时arg[2]为错误信息描述字符串,例如:[string "hello_luatos.lua"]:27: attempt to perform arithmetic on a string value
  76. if not arg[1] then
  77. -- 可以获取到完整的当前协程的调用堆栈信息(包括函数调用链和行号),例如:
  78. -- stack traceback:
  79. -- [string "scheduling.lua"]:27: in function <[string "scheduling.lua"]:19>
  80. local traceBack = debug.traceback(co)
  81. -- arg[2]为错误信息描述:包括文件名,行数,错误信息字符串,例如:
  82. -- [string "scheduling.lua"]:27: attempt to perform arithmetic on a string value
  83. traceBack = (traceBack and traceBack~="") and (arg[2].."\r\n"..traceBack) or arg[2]
  84. log.error("coroutine.resume",traceBack)
  85. --if errDump and type(errDump.appendErr)=="function" then
  86. -- errDump.appendErr(traceBack)
  87. --end
  88. -- 此处COROUTINE_ERROR_ROLL_BACK变量设为了true,表示:
  89. -- 如果某一个协程运行错误,则启动一个500毫秒的定时器,500毫秒之后,执行assert(false, traceBack)
  90. -- 最终表现为:日志中输出traceBack的信息,然后Lua虚拟机异常退出,15秒后自动重启软件;例如:
  91. -- Luat:
  92. -- [string "sys.lua"]:434: [string "scheduling.lua"]:27: attempt to perform arithmetic on a string value
  93. -- stack traceback:
  94. -- [string "scheduling.lua"]:27: in function <[string "scheduling.lua"]:19>
  95. -- Lua VM exit!! reboot in 15000ms
  96. -- 根据这里的代码设计,我们可以知道,如果应用脚本中某一个task(协程)运行异常时,此时其余task的都还可以正常运行
  97. -- 如果不想自动重启软件,则可以在main.lua中的最开始的位置,设置:
  98. -- _G.COROUTINE_ERROR_ROLL_BACK = false
  99. -- _G.COROUTINE_ERROR_RESTART = false
  100. if _G.COROUTINE_ERROR_ROLL_BACK then
  101. sys.timerStart(assert,500,false,traceBack)
  102. elseif _G.COROUTINE_ERROR_RESTART then
  103. rtos.reboot()
  104. end
  105. -- else
  106. -- log.info("coroutine.resume return", unpack(arg))
  107. end
  108. return ...
  109. end
  110. sys.coresume = function(...)
  111. local arg = {...}
  112. -- 此处先执行coroutine.resume(...),表示运行协程,运行时传入了协程对象和处理函数的可变参数
  113. -- coroutine.resume返回多个值:
  114. -- 第一个值是布尔值,表示协程是否执行成功
  115. -- 如果执行成功,第一个返回值为true
  116. -- 后续的返回值是协程运行中,通过coroutine.yield接口使这个协程挂起时传递的参数;
  117. -- 或者是协程正常运行结束的返回值(当协程执行完毕时);
  118. -- 如果执行中出现错误,第一个返回值为false,第二个返回值是错误信息
  119. return wrapper(arg[1], coroutine.resume(...))
  120. end
  121. -- 判断当前运行环境是否在一个协程的处理函数中
  122. -- 如果是,co为协程对象
  123. function sys.check_task()
  124. local co, ismain = coroutine.running()
  125. if ismain then
  126. error(debug.traceback("attempt to yield from outside a coroutine"))
  127. end
  128. return co
  129. end
  130. --- Task任务延时函数,只能用于任务函数中
  131. -- @number ms 整数,最大等待126322567毫秒
  132. -- @return 定时结束返回nil,被其他线程唤起返回调用线程传入的参数
  133. -- @usage sys.wait(30)
  134. function sys.wait(ms)
  135. -- 参数检测,参数不能为负值
  136. --assert(ms > 0, "The wait time cannot be negative!")
  137. -- 判断当前运行环境是否在一个协程的处理函数中
  138. -- 如果是,co为协程对象
  139. local co = sys.check_task()
  140. -- 为定时器申请id
  141. -- 定时器id的取值范围为0 到 TASK_TIMER_ID_MAX - 1
  142. -- 将定时器id和协程对象记录到定时器处理表timerPool中
  143. -- timerPool[timerid] = co
  144. while true do
  145. if taskTimerId >= TASK_TIMER_ID_MAX - 1 then
  146. taskTimerId = 0
  147. else
  148. taskTimerId = taskTimerId + 1
  149. end
  150. if timerPool[taskTimerId] == nil then
  151. break
  152. end
  153. end
  154. local timerid = taskTimerId
  155. timerPool[timerid] = co
  156. -- 调用core的rtos定时器
  157. if 1 ~= rtos.timer_start(timerid, ms) then log.debug("rtos.timer_start error") return end
  158. -- 挂起调用的任务协程
  159. -- 协程恢复时:当再次调用coroutine.resume(co, ...)恢复协程时,resume传入的...参数会作为上此处yield的返回值。
  160. local message = {coroutine.yield()}
  161. -- 下面这段代码适用于以下场景,例如
  162. -- sys.waitUntil("SIM_IND", 120000)
  163. -- 在120秒定时器消息达到之前,首先产生了"SIM_IND"消息,会在上一行代码的coroutine.yield()返回,切换为运行状态
  164. -- 此时执行下面的代码,将正在运行的定时器停止运行,并且删除
  165. if #message ~= 0 then
  166. rtos.timer_stop(timerid)
  167. timerPool[timerid] = nil
  168. return unpack(message)
  169. end
  170. end
  171. --- Task任务的条件等待函数(包括事件消息和定时器消息等条件),只能用于任务函数中。
  172. -- @param id 消息ID
  173. -- @number ms 等待超时时间,单位ms,最大等待126322567毫秒
  174. -- @return result 接收到消息返回true,超时返回false
  175. -- @return data 接收到消息返回消息参数
  176. -- @usage result, data = sys.waitUntil("SIM_IND", 120000)
  177. function sys.waitUntil(id, ms)
  178. -- 判断当前运行环境是否在一个协程的处理函数中
  179. -- 如果是,co为协程对象
  180. local co = sys.check_task()
  181. -- 在全局消息订阅表subscribers中添加一条记录
  182. -- subscribers[id][co] = true
  183. -- 表示当前协程需要处理id表示的消息
  184. sys.subscribe(id, co)
  185. -- 如果传入了ms参数,表示配置了等待超时参数
  186. -- 此时直接执行sys.wait(ms):在wait函数内部,创建运行定时器,并且挂起当前协程
  187. -- 当定时器超时消息到达或者id表示的消息达到时,当前协程切换为运行状态
  188. -- 当定时器超时消息触发的状态切换,此时返回值message为{sys.wait(ms)},为{}
  189. -- 当id表示的消息触发的状态切换,此时返回值message为{sys.wait(ms)},为{id, {...}}
  190. -- 如果没有传入ms参数,表示一直在等待id表示的消息
  191. -- 此时直接执行下一行代码中的coroutine.yield(),挂起当前协程
  192. -- 当id表示的消息达到时,当前协程切换为运行状态
  193. -- 此时返回值message为{coroutine.yield()},为{id, {...}}
  194. local message = ms and {sys.wait(ms)} or {coroutine.yield()}
  195. -- 在全局消息订阅表subscribers中清除一条记录
  196. -- subscribers[id][co] = nil
  197. -- 表示当前协程不再需要处理id表示的消息
  198. sys.unsubscribe(id, co)
  199. -- 如果是id表示的消息到达退出了阻塞等待状态,则message[1] ~= nil为true
  200. -- 如果是定时器超时消息到达退出了阻塞等待状态,则message[1] ~= nil为false
  201. return message[1] ~= nil, unpack(message, 2, #message)
  202. end
  203. -- 此函数不给外部使用,忽略
  204. --- 同上,但不返回等待结果
  205. function sys.waitUntilMsg(id)
  206. local co = sys.check_task()
  207. sys.subscribe(id, co)
  208. local message = {coroutine.yield()}
  209. sys.unsubscribe(id, co)
  210. return unpack(message, 2, #message)
  211. end
  212. -- 此函数不给外部使用,忽略
  213. --- Task任务的条件等待函数扩展(包括事件消息和定时器消息等条件),只能用于任务函数中。
  214. -- @param id 消息ID
  215. -- @number ms 等待超时时间,单位ms,最大等待126322567毫秒
  216. -- @return message 接收到消息返回message,超时返回false
  217. -- @return data 接收到消息返回消息参数
  218. -- @usage result, data = sys.waitUntilExt("SIM_IND", 120000)
  219. function sys.waitUntilExt(id, ms)
  220. local co = sys.check_task()
  221. sys.subscribe(id, co)
  222. local message = ms and {sys.wait(ms)} or {coroutine.yield()}
  223. sys.unsubscribe(id, co)
  224. if message[1] ~= nil then return unpack(message) end
  225. return false
  226. end
  227. --- 创建一个任务线程,在模块最末行调用该函数并注册模块中的任务函数,main.lua导入该模块即可
  228. -- @param fun 任务函数名,用于resume唤醒时调用
  229. -- @param ... 任务函数fun的可变参数
  230. -- @return co 返回该任务的线程号
  231. -- @usage sys.taskInit(task1,'a','b')
  232. function sys.taskInit(fun, ...)
  233. -- 创建协程,返回协程对象co
  234. -- 创建一个Lua协程,协程的处理函数为fun,返回一个协程对象co
  235. -- 执行这一行代码之后,这个协程并不会运行,而是处于挂起状态
  236. local co = coroutine.create(fun)
  237. -- 启动运行协程,传入协程对象和任务处理函数携带的可变参数
  238. sys.coresume(co, ...)
  239. return co
  240. end
  241. ------------------------------------------ rtos消息回调处理部分 ------------------------------------------
  242. --[[
  243. 函数名:cmpTable
  244. 功能 :比较两个table的内容是否相同,注意:table中不能再包含table
  245. 参数 :
  246. t1:第一个table
  247. t2:第二个table
  248. 返回值:相同返回true,否则false
  249. ]]
  250. local function cmpTable(t1, t2)
  251. if not t2 then return #t1 == 0 end
  252. if #t1 == #t2 then
  253. for i = 1, #t1 do
  254. if unpack(t1, i, i) ~= unpack(t2, i, i) then
  255. return false
  256. end
  257. end
  258. return true
  259. end
  260. return false
  261. end
  262. --- 关闭定时器
  263. -- @param val 值为number时,识别为定时器ID,值为回调函数时,需要传参数
  264. -- @param ... val值为函数时,函数的可变参数
  265. -- @return 无
  266. -- @usage timerStop(1)
  267. function sys.timerStop(val, ...)
  268. -- val为number类型时,表示定时器id
  269. if type(val) == 'number' then
  270. -- 在定时器处理表timerPool中,清空定时器id val对应的处理函数记录
  271. -- 在定时器回调函数参数表para中,清空定时器id val对应的回调函数的参数记录
  272. timerPool[val], para[val] = nil, nil
  273. -- 调用内核固件的接口,停止并且删除定时器id val对应的定时器
  274. rtos.timer_stop(val)
  275. -- val为其他类型时(实际上仅支持function类型),表示定时器的回调函数
  276. else
  277. for k, v in pairs(timerPool) do
  278. -- 根据回调函数,从定时器处理表timerPool中找到定时器id
  279. -- v为function类型,下一行代码实际上判断的是v == val
  280. if type(v) == 'table' and v.cb == val or v == val then
  281. -- 根据定时器id,从定时器会回调函数参数表中,判断可变参数相同
  282. if cmpTable({...}, para[k]) then
  283. rtos.timer_stop(k)
  284. timerPool[k], para[k] = nil, nil
  285. break
  286. end
  287. end
  288. end
  289. end
  290. end
  291. --- 关闭同一回调函数的所有定时器
  292. -- @param fnc 定时器回调函数
  293. -- @return 无
  294. -- @usage timerStopAll(cbFnc)
  295. function sys.timerStopAll(fnc)
  296. for k, v in pairs(timerPool) do
  297. -- 根据回调函数,从定时器处理表timerPool中找到定时器id
  298. -- v为function类型,下一行代码实际上判断的是v == fnc
  299. if type(v) == "table" and v.cb == fnc or v == fnc then
  300. rtos.timer_stop(k)
  301. timerPool[k], para[k] = nil, nil
  302. end
  303. end
  304. end
  305. function sys.timerAdvStart(fnc, ms, _repeat, ...)
  306. --回调函数和时长检测
  307. --assert(fnc ~= nil, "sys.timerStart(first param) is nil !")
  308. --assert(ms > 0, "sys.timerStart(Second parameter) is <= zero !")
  309. -- 关闭完全相同的定时器
  310. -- 定时器回调函数+回调参数的方式,可以唯一标识一个定时器
  311. -- 所以此处调用sys.timerStop接口,传入回调函数+回调参数,就可以关闭之前的重复定时器
  312. local arg = {...}
  313. if #arg == 0 then
  314. sys.timerStop(fnc)
  315. else
  316. sys.timerStop(fnc, ...)
  317. end
  318. -- 为定时器申请id
  319. -- 定时器id的取值范围为TASK_TIMER_ID_MAX +1 到 MSG_TIMER_ID_MAX
  320. -- 将定时器id和定时器回调函数记录到定时器处理表timerPool中
  321. -- timerPool[msgId] = fnc
  322. while true do
  323. if msgId >= MSG_TIMER_ID_MAX then msgId = TASK_TIMER_ID_MAX end
  324. msgId = msgId + 1
  325. if timerPool[msgId] == nil then
  326. timerPool[msgId] = fnc
  327. break
  328. end
  329. end
  330. --调用内核固件接口创建并且启动定时器
  331. if rtos.timer_start(msgId, ms, _repeat) ~= 1 then
  332. log.error("rtos.timer_start", "create fail!!!")
  333. return
  334. end
  335. --如果存在回调函数的可变参数,在定时器回调函数参数表para中保存参数
  336. if #arg ~= 0 then
  337. para[msgId] = arg
  338. end
  339. --返回定时器id
  340. return msgId
  341. end
  342. --- 开启一个定时器
  343. -- @param fnc 定时器回调函数
  344. -- @number ms 整数,最大定时126322567毫秒
  345. -- @param ... 可变参数 fnc的参数
  346. -- @return number 定时器ID,如果失败,返回nil
  347. function sys.timerStart(fnc, ms, ...)
  348. return sys.timerAdvStart(fnc, ms, 0, ...)
  349. end
  350. --- 开启一个循环定时器
  351. -- @param fnc 定时器回调函数
  352. -- @number ms 整数,最大定时126322567毫秒
  353. -- @param ... 可变参数 fnc的参数
  354. -- @return number 定时器ID,如果失败,返回nil
  355. function sys.timerLoopStart(fnc, ms, ...)
  356. return sys.timerAdvStart(fnc, ms, -1, ...)
  357. end
  358. --- 判断某个定时器是否处于开启状态
  359. -- @param val 有两种形式
  360. --一种是开启定时器时返回的定时器id,此形式时不需要再传入可变参数...就能唯一标记一个定时器
  361. --另一种是开启定时器时的回调函数,此形式时必须再传入可变参数...才能唯一标记一个定时器
  362. -- @param ... 可变参数
  363. -- @return number 开启状态返回true,否则nil
  364. function sys.timerIsActive(val, ...)
  365. -- 根据定时器id来判断
  366. if type(val) == "number" then
  367. return timerPool[val]
  368. -- 根据定时器回调函数和回调参数来判断
  369. else
  370. for k, v in pairs(timerPool) do
  371. if v == val then
  372. if cmpTable({...}, para[k]) then return true end
  373. end
  374. end
  375. end
  376. end
  377. ------------------------------------------ LUA应用消息订阅/发布接口 ------------------------------------------
  378. -- 全局消息订阅表
  379. -- 数据结构如下:
  380. -- local subscribers =
  381. -- {
  382. -- [msg1] =
  383. -- {
  384. -- cbfunc1 = true, -- sys.subscribe(msg1, cbfunc1)
  385. -- task1 = true, -- 在task1的处理函数中,调用sys.waitUntil(msg1[, timeout])
  386. -- cbfunc2 = true,
  387. -- task2 = true,
  388. -- ......
  389. -- cbfuncN = true,
  390. -- taskN = true,
  391. -- },
  392. -- [msg2] = { ...... },
  393. -- [msg3] = { ...... },
  394. -- ......,
  395. -- [msgN] = { ...... }
  396. -- }
  397. local subscribers = {}
  398. -- 全局消息队列
  399. -- 数据结构如下:
  400. -- local messageQueue =
  401. -- {
  402. -- [1] = {msg1, {...}}, --sys.publish(msg1, ...)
  403. -- [2] = {msg2, {...}},
  404. -- [3] = {msg3, {...}},
  405. -- ......,
  406. -- [n] = { ...... }
  407. -- }
  408. local messageQueue = {}
  409. --- 订阅消息
  410. -- @param id 消息id
  411. -- @param callback 消息回调处理
  412. -- @usage subscribe("NET_STATUS_IND", callback)
  413. function sys.subscribe(id, callback)
  414. --if not id or type(id) == "boolean" or (type(callback) ~= "function" and type(callback) ~= "thread") then
  415. -- log.warn("warning: sys.subscribe invalid parameter", id, callback)
  416. -- return
  417. --end
  418. --log.debug("sys", "subscribe", id, callback)
  419. -- 下面这段if语句的使用方法,在api文档中没有介绍,在demo代码中从来没有用到,忽略
  420. if type(id) == "table" then
  421. -- 支持多topic订阅
  422. for _, v in pairs(id) do sys.subscribe(v, callback) end
  423. return
  424. end
  425. -- 下面两行代码
  426. -- 在全局消息订阅表中,增加一条“消息和对应的回调函数”的记录
  427. if not subscribers[id] then subscribers[id] = {} end
  428. subscribers[id][callback] = true
  429. end
  430. --- 取消订阅消息
  431. -- @param id 消息id
  432. -- @param callback 消息回调处理
  433. -- @usage unsubscribe("NET_STATUS_IND", callback)
  434. function sys.unsubscribe(id, callback)
  435. --if not id or type(id) == "boolean" or (type(callback) ~= "function" and type(callback) ~= "thread") then
  436. -- log.warn("warning: sys.unsubscribe invalid parameter", id, callback)
  437. -- return
  438. --end
  439. --log.debug("sys", "unsubscribe", id, callback)
  440. -- 下面这段if语句的使用方法,在api文档中没有介绍,在demo代码中从来没有用到,忽略
  441. if type(id) == "table" then
  442. -- 支持多topic订阅
  443. for _, v in pairs(id) do sys.unsubscribe(v, callback) end
  444. return
  445. end
  446. -- 下面的if else语句
  447. -- 在全局消息订阅表中,删除一条“消息和对应的回调函数”的记录
  448. if subscribers[id] then
  449. subscribers[id][callback] = nil
  450. else
  451. return
  452. end
  453. -- 下面的几行代码
  454. -- 判断一下当前消息是否还有其他的订阅者
  455. -- 如果没有任何订阅者,则清除消息的记录
  456. for k, _ in pairs(subscribers[id]) do
  457. return
  458. end
  459. subscribers[id] = nil
  460. end
  461. --- 发布内部消息,存储在内部消息队列中
  462. -- @param ... 可变参数,用户自定义
  463. -- @return 无
  464. -- @usage publish("NET_STATUS_IND")
  465. function sys.publish(...)
  466. -- 在全局消息队列末尾插入记录一条全局消息
  467. table.insert(messageQueue, {...})
  468. end
  469. -- 循环分发处理全局消息队列messageQueue中的所有消息
  470. local function dispatch()
  471. while true do
  472. if #messageQueue == 0 then
  473. break
  474. end
  475. -- 从全局消息队列messageQueue中取出来队首的消息
  476. local message = table.remove(messageQueue, 1)
  477. -- 根据取出的队首消息,在全部消息订阅表中查找是否有订阅者
  478. if subscribers[message[1]] then
  479. local tmpt = {}
  480. -- 将消息的所有订阅者(回调函数和task对象)临时存放到tmpt中
  481. for callback, _ in pairs(subscribers[message[1]]) do
  482. table.insert(tmpt, callback)
  483. end
  484. -- 遍历所有订阅者
  485. -- 根据订阅者类型,执行回调函数或者恢复运行task
  486. for _, callback in ipairs(tmpt) do
  487. if type(callback) == "function" then
  488. callback(unpack(message, 2, #message))
  489. elseif type(callback) == "thread" then
  490. sys.coresume(callback, unpack(message))
  491. end
  492. end
  493. end
  494. end
  495. end
  496. -- rtos消息回调
  497. --local handlers = {}
  498. --setmetatable(handlers, {__index = function() return function() end end, })
  499. --- 注册rtos消息回调处理函数
  500. -- @number id 消息类型id
  501. -- @param handler 消息处理函数
  502. -- @return 无
  503. -- @usage rtos.on(rtos.MSG_KEYPAD, function(param) handle keypad message end)
  504. --function sys.on(id, handler)
  505. -- handlers[id] = handler
  506. --end
  507. ------------------------------------------ Luat 主调度框架 ------------------------------------------
  508. function sys.safeRun()
  509. -- 循环分发处理全局消息队列messageQueue中的所有消息
  510. dispatch()
  511. -- 阻塞读取内核消息队列中第一条消息
  512. local msg, param, exparam = rtos.receive(rtos.INF_TIMEOUT)
  513. -- log.info("rtos.receive", msg, param, exparam)
  514. -- 空消息?
  515. if not msg or msg == 0 then
  516. -- 如果走到了这里,说明rtos.receive在内核固件的业务逻辑中:
  517. -- 已经取出了一条消息,并且在内核固件中自动处理了,例如:
  518. -- uart接收到新数据的中断消息,会在rtos.receive中自动调用uart.on(uart_id, "receive", cbfun)中的cbfun
  519. -- log.info("sys.safeRun", "needn't handle")
  520. -- 从内核消息队列中读到了定时器消息
  521. -- msg为rtos.MSG_TIMER
  522. -- param为定时器id
  523. -- exparam表示是否为循环定时器
  524. elseif msg == rtos.MSG_TIMER and timerPool[param] then
  525. -- 任务定时器类型
  526. if param < TASK_TIMER_ID_MAX then
  527. local taskId = timerPool[param]
  528. -- 任务定时器只能是单次定时器,所以从定时器处理表中删除此定时器记录
  529. timerPool[param] = nil
  530. -- 将等待定时器消息的任务,由挂起阻塞状态切换为运行状态
  531. sys.coresume(taskId)
  532. -- 消息定时器类型
  533. else
  534. -- 取出来当前定时器对应的回调函数
  535. local cb = timerPool[param]
  536. -- 如果不是循环定时器,从定时器处理表中删除此定时器记录
  537. if exparam == 0 then timerPool[param] = nil end
  538. -- 当前定时器的回调函数有回调参数
  539. if para[param] ~= nil then
  540. cb(unpack(para[param]))
  541. -- 如果不是循环定时器,从定时器回调函数参数表中删除此定时器记录
  542. if exparam == 0 then para[param] = nil end
  543. -- 当前定时器的回调函数没有回调参数
  544. else
  545. cb()
  546. end
  547. --如果是循环定时器,继续启动此定时器
  548. --if loop[param] then rtos.timer_start(param, loop[param]) end
  549. end
  550. --其他消息(音频消息、充电管理消息、按键消息等)
  551. --elseif type(msg) == "number" then
  552. -- handlers[msg](param, exparam)
  553. --else
  554. -- handlers[msg.id](msg)
  555. end
  556. end
  557. --- run()从底层获取core消息并及时处理相关消息,查询定时器并调度各注册成功的任务线程运行和挂起
  558. -- @return 无
  559. -- @usage sys.run()
  560. -- log.info("_G.SYSP", _G.SYSP)
  561. -- _G.SYSP为nil,所以此处定义了sys.run()就是一直在循环执行sys.safeRun()
  562. if _G.SYSP then
  563. function sys.run() end
  564. else
  565. function sys.run()
  566. while true do
  567. sys.safeRun()
  568. end
  569. end
  570. end
  571. -- _G是Lua的全局环境表
  572. -- 这里给_G定义了一个全局函数sys_pub
  573. -- sys_pub的作用是:内核固件的C代码中直接发布全局消息,Lua脚本中订阅此全局消息的应用都可以接收处理,例如:网络环境准备就绪的消息"IP_READY"
  574. _G.sys_pub = sys.publish
  575. -- 并入原本的sysplus
  576. -- 以下给异步C接口使用的几个api,在sys核心库中,没有开发给用户直接使用;忽略
  577. ----------------------------------------------
  578. -- 提供给异步c接口使用, by 晨旭
  579. sys.cwaitMt = {
  580. wait = function(t,r)
  581. return function()
  582. if r and type(r) == "table" then--新建等待失败的返回
  583. return table.unpack(r)
  584. end
  585. return sys.waitUntilMsg(t)
  586. end
  587. end,
  588. cb = function(t,r)
  589. return function(f)
  590. if type(f) ~= "function" then return end
  591. sys.taskInit(function ()
  592. if r and type(r) == "table" then
  593. --sys.wait(1)--如果回调里调用了sys.publish,直接调用回调,会触发不了下一行的吧。。。
  594. f(table.unpack(r))
  595. return
  596. end
  597. f(sys.waitUntilMsg(t))
  598. end)
  599. end
  600. end,
  601. }
  602. sys.cwaitMt.__index = function(t,i)
  603. if sys.cwaitMt[i] then
  604. return sys.cwaitMt[i](rawget(t,"w"),rawget(t,"r"))
  605. else
  606. rawget(t,i)
  607. end
  608. end
  609. _G.sys_cw = function (w,...)
  610. local r = {...}
  611. local t = {w=w,r=(#r > 0 and r or nil)}
  612. setmetatable(t,sys.cwaitMt)
  613. return t
  614. end
  615. -------------------------------------------------------------------
  616. ------------- 基于任务的task扩展 by 李思琦---------------------------
  617. -- 高级task的任务列表
  618. -- 数据结构如下:
  619. -- local taskList =
  620. -- {
  621. -- ["task_name1"] =
  622. -- {
  623. -- -- 定向消息队列,用来存储sys.sendMsg("task_name1", param1, param2, param3, param4)给"task_name1"的高级task发布的param1, param2, param3, param4消息参数
  624. -- -- table.insert(taskList["task_name1"].msgQueue, {param1, param2, param3, param4})
  625. -- msgQueue = {{param1, param2, param3, param4}, {}, ...},
  626. -- -- sys.waitMsg(taskName, target, ms)时,如果传入了ms超时时长,并且当前task在阻塞等待target消息;
  627. -- -- 则To表示阻塞等待过程中,是否因为超时原因退出了阻塞等待状态
  628. -- To = false,
  629. -- -- 非目标消息回调函数
  630. -- cb = cbFun
  631. -- },
  632. -- ["task_name2"] =
  633. -- {
  634. -- },
  635. --
  636. -- ...
  637. -- }
  638. local taskList = {}
  639. --- 创建一个任务线程,在模块最末行调用该函数并注册模块中的任务函数,main.lua导入该模块即可
  640. -- @param fun 任务函数名,用于resume唤醒时调用
  641. -- @param taskName 任务名称,用于唤醒任务的id
  642. -- @param cbFun 接收到非目标消息时的回调函数
  643. -- @param ... 任务函数fun的可变参数
  644. -- @return co 返回该任务的线程号
  645. -- @usage sys.taskInitEx(task1,'a',callback)
  646. function sys.taskInitEx(fun, taskName, cbFun, ...)
  647. if taskName == nil then
  648. log.error("sys", "taskName is nil", debug.traceback())
  649. return
  650. end
  651. -- 在高级task任务列表中,申请此高级任务对应的定向消息队列、阻塞等待定向消息超时标志、非目标消息回调函数
  652. taskList[taskName]={msgQueue={}, To=false, cb=cbFun}
  653. -- 创建一个基础task
  654. return sys.taskInit(fun, ...)
  655. end
  656. --- 删除由taskInitEx创建的任务线程
  657. -- @param taskName 任务名称,用于唤醒任务的id
  658. -- @return 无
  659. -- @usage sys.taskDel('a')
  660. function sys.taskDel(taskName)
  661. -- 释放taskName对应的高级task全部信息表资源,
  662. -- 释放的是taskList[taskName] 对应的 {msgQueue={}, To=false, cb=cbFun}占用的内存
  663. -- 因为taskList是全局变量,会一直存在;
  664. -- 如果taskList[taskName]没有赋值为nil,则taskList[taskName]一直引用{msgQueue={}, To=false, cb=cbFun}
  665. -- 此时Lua的垃圾回收机制永远不会自动回收{msgQueue={}, To=false, cb=cbFun}所占用的内存
  666. -- 所以在高级task的任务处理函数执行结束,每个return语句正常退出前,或者最后执行结束自然退出前,
  667. -- 都要调用sys.taskDel(taskName),释放{msgQueue={}, To=false, cb=cbFun}占用的内存
  668. -- 如果高级task的任务处理函数运行过程中,发生异常退出,这种情况下,即使我们写了sys.taskDel(taskName),也不会被执行
  669. -- 遇到这种问题,需要怎么处理呢?分为以下两种情况:
  670. -- 1、目前的LuatOS设计,当task运行异常退出,默认会自动重启软件系统,
  671. -- 这种情况下,软件都重启了,就不用考虑未释放的内存问题了;
  672. -- 2、如果你看懂了sys.lua的内部设计,当task运行异常退出时,可以控制不重启软件系统,
  673. -- 这种情况下,就需要修改sys.lua的内部设计,可以自动释放异常退出的高级task对应的信息表资源内存,
  674. -- 目前还没有实现这种功能,后续如果客户有需求,可以开发支持
  675. taskList[taskName]=nil
  676. end
  677. -- 等待定向消息定时器回调函数
  678. local function waitTo(taskName)
  679. taskList[taskName].To = true
  680. sys.publish(taskName)
  681. end
  682. --- 等待接收一个目标消息
  683. -- @param taskName 任务名称,用于唤醒任务的id
  684. -- @param target 目标消息,如果为nil,则表示接收到任意消息都会退出
  685. -- @param ms 超时时间,如果为nil,则表示无超时,永远等待
  686. -- @return msg or false 成功返回table型的msg,超时返回false
  687. -- @usage sys.waitMsg('a', 'b', 1000)
  688. function sys.waitMsg(taskName, target, ms)
  689. -- 此接口只推荐在sys.taskInitEx创建的高级task的任务处理函数中使用
  690. if taskList[taskName] == nil then
  691. log.error("sys", "sys.taskInitEx启动的task才能使用waitMsg")
  692. return false
  693. end
  694. local msg = false
  695. local message = nil
  696. -- 如果定向消息队列中有消息
  697. while #taskList[taskName].msgQueue > 0 do
  698. -- 取出来队首的消息
  699. msg = table.remove(taskList[taskName].msgQueue, 1)
  700. -- 如果没有指定期望接收的消息,则表示期望接收任何消息
  701. -- 直接返回队首的消息给用户脚本去处理
  702. if target == nil then
  703. return msg
  704. end
  705. -- 如果队首消息是期望处理的消息
  706. -- 直接返回队首的消息给用户脚本去处理
  707. if (msg[1] == target) then
  708. return msg
  709. -- 如果队首消息不是期望处理的消息,并且也传入了非目标消息回调函数
  710. -- 则直接将队首消息丢给非目标消息回调函数去处理
  711. -- 然后while循环继续判断下一条消息
  712. elseif type(taskList[taskName].cb) == "function" then
  713. taskList[taskName].cb(msg)
  714. end
  715. end
  716. -- 走到了这里,说明定向消息队列中已经没有消息
  717. -- 使用当前task对象订阅名称为taskName的全局消息
  718. sys.subscribe(taskName, coroutine.running())
  719. -- 如果传入了ms参数,启动一个等待定向目标消息超时定时器
  720. sys.timerStop(waitTo, taskName)
  721. if ms and ms ~= 0 then
  722. sys.timerStart(waitTo, ms, taskName)
  723. end
  724. taskList[taskName].To = false
  725. local finish=false
  726. while not finish do
  727. -- 阻塞挂起当前task
  728. -- 以下两种情况,会退出挂起状态,切换为运行状态:
  729. -- 1、当调用sys.sendMsg接口向此定向消息队列中发送一条消息时,会publish一条名称为taskName的全局消息
  730. -- 在sys.run()下次调度时,会分发处理这条全局消息,发现当前task订阅了这条全局消息,所以将当前task切换为运行状态
  731. -- 2、等待定向目标消息超时定时器超时后,在waitTo函数中,也会publish一条名称为taskName的全局消息
  732. -- 在sys.run()下次调度时,会分发处理这条全局消息,发现当前task订阅了这条全局消息,所以将当前task切换为运行状态
  733. message = coroutine.yield()
  734. -- 如果定向消息队列中有消息
  735. if #taskList[taskName].msgQueue > 0 then
  736. -- 取出来队首的消息
  737. msg = table.remove(taskList[taskName].msgQueue, 1)
  738. -- sys.info("check target", msg[1], target)
  739. -- 如果没有指定期望接收的消息,则表示期望接收任何消息
  740. -- 退出while循环
  741. if target == nil then
  742. finish = true
  743. else
  744. -- 如果队首消息是期望处理的消息
  745. -- 退出while循环
  746. if (msg[1] == target) then
  747. finish = true
  748. -- 如果队首消息不是期望处理的消息,并且也传入了非目标消息回调函数
  749. -- 则直接将队首消息丢给非目标消息回调函数去处理
  750. -- 不退出wbile循环,下次循环继续判断消息队列中的下一条消息
  751. elseif type(taskList[taskName].cb) == "function" then
  752. taskList[taskName].cb(msg)
  753. end
  754. end
  755. -- 如果定向消息队列中没有消息,则表示此次恢复运行时定时器超时消息触发
  756. -- 退出此while循环
  757. elseif taskList[taskName].To then
  758. -- sys.info(taskName, "wait message timeout")
  759. finish = true
  760. end
  761. end
  762. -- 定时器超时消息触发,结束等待消息
  763. -- 这种情况下,表示没有等到目标消息
  764. if taskList[taskName].To then
  765. msg = nil
  766. end
  767. -- 复位定时器超时标志,关闭定时器,取消全局消息订阅
  768. taskList[taskName].To = false
  769. sys.timerStop(waitTo, taskName)
  770. sys.unsubscribe(taskName, coroutine.running())
  771. -- 接收到目标消息,返回table类型的定向消息{msg, para2, para3, para4}
  772. -- 超时返回nil
  773. return msg
  774. end
  775. --- 向目标任务发送一个消息
  776. -- @param taskName 任务名称,用于唤醒任务的id
  777. -- @param param1 消息中的参数1,同时也是waitMsg里的target
  778. -- @param param2 消息中的参数2
  779. -- @param param3 消息中的参数3
  780. -- @param param4 消息中的参数4
  781. -- @return true or false 成功返回true
  782. -- @usage sys.sendMsg('a', 'b')
  783. function sys.sendMsg(taskName, param1, param2, param3, param4)
  784. if taskList[taskName]~=nil then
  785. -- 向taskName所表示的高级task的定向消息队列中,插入一条定向消息
  786. table.insert(taskList[taskName].msgQueue, {param1, param2, param3, param4})
  787. -- 此处发布一条名称为taskName的全局消息,此消息的作用分为以下两种情况来描述:
  788. -- 1、如果taskName所表示的高级task正在sys.waitMsg接口阻塞等待接收消息,
  789. -- 则可以控制task切换为运行状态,从定向消息队列中读消息进行后续处理
  790. -- 2、如果taskName所表示的高级task不在sys.waitMsg接口阻塞等待接收消息,
  791. -- 则此处的publish消息接口没有任何作用,在下次sys.run()取出此消息后,会自动丢弃,不做任何处理
  792. sys.publish(taskName)
  793. return true
  794. end
  795. return false
  796. end
  797. -- 清空taskName所表示的高级task定向消息队列中的所有消息
  798. function sys.cleanMsg(taskName)
  799. if taskList[taskName]~=nil then
  800. taskList[taskName].msgQueue = {}
  801. return true
  802. end
  803. return false
  804. end
  805. -- 这个函数目前没有直接开放给用户使用,忽略
  806. function sys.taskCB(taskName, msg)
  807. if taskList[taskName]~=nil then
  808. if type(taskList[taskName].cb) == "function" then
  809. taskList[taskName].cb(msg)
  810. return
  811. end
  812. end
  813. log.error(taskName, "no cb fun")
  814. end
  815. -- _G是Lua的全局环境表
  816. -- 这里给_G定义了两个全局函数sys_send和sys_wait
  817. -- sys_send的作用是:内核固件的C代码中直接发送定向消息给Lua脚本中的高级task,例如socket.EVENT类型的消息
  818. -- sys_wait的作用是:目前没有直接开放给用户使用,忽略
  819. _G.sys_send = sys.sendMsg
  820. _G.sys_wait = sys.waitMsg
  821. return sys
  822. ----------------------------