sys.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  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.4.4"
  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. -- 启动GSM协议栈。例如在充电开机未启动GSM协议栈状态下,如果用户长按键正常开机,此时调用此接口启动GSM协议栈即可
  29. -- @return 无
  30. -- @usage sys.powerOn()
  31. function powerOn()
  32. rtos.poweron(1)
  33. end
  34. --- 软件重启
  35. -- @string r 重启原因,用户自定义,一般是string类型,重启后的trace中会打印出此重启原因
  36. -- @return 无
  37. -- @usage sys.restart('程序超时软件重启')
  38. function restart(r)
  39. assert(r and r ~= "", "sys.restart cause null")
  40. if errDump and errDump.appendErr and type(errDump.appendErr) == "function" then errDump.appendErr("restart[" .. r .. "];") end
  41. log.warn("sys.restart", r)
  42. rtos.restart()
  43. end
  44. --- task任务延时函数
  45. -- 只能直接或者间接的被task任务主函数调用,如果定时器创建成功,则本task会挂起
  46. -- @number ms 延时时间,单位毫秒,最小1,最大0x7FFFFFFF
  47. -- 实际上支持的最小超时时间是5毫秒,小于5毫秒的时间都会被转化为5毫秒
  48. -- @return result,分为如下三种情况:
  49. -- 1、如果定时器创建失败,本task不会被挂起,直接返回nil
  50. -- 2、如果定时器创建成功,本task被挂起,超时时间到达后,会激活本task,返回nil
  51. -- 3、如果定时器创建成功,本task被挂起,在超时时间到达之前,其他业务逻辑主动激活本task,
  52. -- 返回激活时携带的可变参数(如果不是故意为之,可能是写bug了)
  53. -- @usage
  54. -- task延时5秒:
  55. -- sys.taskInit(function()
  56. -- sys.wait(5000)
  57. -- end)
  58. function wait(ms)
  59. -- 参数检测,参数不能为负值
  60. assert(ms > 0, "The wait time cannot be negative!")
  61. --4G底层不支持小于5ms的定时器
  62. if ms < 5 then ms = 5 end
  63. -- 选一个未使用的定时器ID给该任务线程
  64. if taskTimerId >= TASK_TIMER_ID_MAX then taskTimerId = 0 end
  65. taskTimerId = taskTimerId + 1
  66. local timerid = taskTimerId
  67. taskTimerPool[coroutine.running()] = timerid
  68. timerPool[timerid] = coroutine.running()
  69. -- 调用core的rtos定时器
  70. if 1 ~= rtos.timer_start(timerid, ms) then log.debug("rtos.timer_start error") return end
  71. -- 挂起调用的任务线程
  72. local message = {coroutine.yield()}
  73. if #message ~= 0 then
  74. rtos.timer_stop(timerid)
  75. taskTimerPool[coroutine.running()] = nil
  76. timerPool[timerid] = nil
  77. return unpack(message)
  78. end
  79. end
  80. --- task任务条件等待函数(支持事件消息和定时器消息)
  81. -- 只能直接或者间接的被task任务主函数调用,调用本接口的task会挂起
  82. -- @string id 消息ID,建议使用string类型
  83. -- @number[opt=nil] ms 延时时间,单位毫秒,最小1,最大0x7FFFFFFF
  84. -- 实际上支持的最小超时时间是5毫秒,小于5毫秒的时间都会被转化为5毫秒
  85. -- @return result,data,分为如下三种情况:
  86. -- 1、如果存在超时时间参数:
  87. -- (1)、在超时时间到达之前,如果收到了等待的消息ID,则result为true,data为消息ID携带的参数(可能是多个参数)
  88. -- (2)、在超时时间到达之前,如果没收到等待的消息ID,则result为false,data为nil
  89. -- 2、如果不存在超时时间参数:如果收到了等待的消息ID,则result为true,data为消息ID携带的参数(可能是多个参数)
  90. -- (1)、如果收到了等待的消息ID,则result为true,data为消息ID携带的参数(可能是多个参数)
  91. -- (2)、如果没收到等待的消息ID,则task一直挂起
  92. -- 3、还存在一种特殊情况,本task挂起时,可能被task的外部应用逻辑给主动激活(如果不是故意为之,可能是写bug了)
  93. -- @usage
  94. -- task延时120秒或者收到"SIM_IND"消息:
  95. -- sys.taskInit(function()
  96. -- local result, data = sys.waitUntil("SIM_IND",120000)
  97. -- end)
  98. function waitUntil(id, ms)
  99. subscribe(id, coroutine.running())
  100. local message = ms and {wait(ms)} or {coroutine.yield()}
  101. unsubscribe(id, coroutine.running())
  102. return message[1] ~= nil, unpack(message, 2, #message)
  103. end
  104. --- Task任务的条件等待函数扩展(包括事件消息和定时器消息等条件),只能用于任务函数中。
  105. -- @param id 消息ID
  106. -- @number ms 等待超时时间,单位ms,最大等待126322567毫秒
  107. -- @return message 接收到消息返回message,超时返回false
  108. -- @return data 接收到消息返回消息参数
  109. -- @usage result, data = sys.waitUntilExt("SIM_IND", 120000)
  110. function waitUntilExt(id, ms)
  111. subscribe(id, coroutine.running())
  112. local message = ms and {wait(ms)} or {coroutine.yield()}
  113. unsubscribe(id, coroutine.running())
  114. if message[1] ~= nil then return unpack(message) end
  115. return false
  116. end
  117. --- 创建一个任务并且运行该任务
  118. -- @param fun 任务主函数,激活task时使用
  119. -- @param ... 任务主函数fun的可变参数
  120. -- @return co 返回该任务的线程ID
  121. -- @usage sys.taskInit(task1,'a','b')
  122. function taskInit(fun, ...)
  123. local co = coroutine.create(fun)
  124. coroutine.resume(co, ...)
  125. return co
  126. end
  127. --- Luat平台初始化
  128. -- @param mode 充电开机是否启动GSM协议栈,1不启动,否则启动
  129. -- @param lprfnc 用户应用脚本中定义的“低电关机处理函数”,如果有函数名,则低电时,本文件中的run接口不会执行任何动作,否则,会延时1分钟自动关机
  130. -- @return 无
  131. -- @usage sys.init(1,0)
  132. function init(mode, lprfnc)
  133. -- 用户应用脚本中必须定义PROJECT和VERSION两个全局变量,否则会死机重启,如何定义请参考各个demo中的main.lua
  134. assert(PROJECT and PROJECT ~= "" and VERSION and VERSION ~= "", "Undefine PROJECT or VERSION")
  135. collectgarbage("setpause", 80)
  136. -- 设置AT命令的虚拟串口
  137. uart.setup(uart.ATC, 0, 0, uart.PAR_NONE, uart.STOP_1)
  138. log.info("poweron reason:", rtos.poweron_reason(), PROJECT, VERSION, SCRIPT_LIB_VER, rtos.get_version())
  139. pcall(rtos.set_lua_info,"\r\n"..rtos.get_version().."\r\n"..(_G.PROJECT or "NO PROJECT").."\r\n"..(_G.VERSION or "NO VERSION"))
  140. if type(rtos.get_build_time)=="function" then log.info("core build time", rtos.get_build_time()) end
  141. if mode == 1 then
  142. -- 充电开机
  143. if rtos.poweron_reason() == rtos.POWERON_CHARGER then
  144. -- 关闭GSM协议栈
  145. rtos.poweron(0)
  146. end
  147. end
  148. end
  149. ------------------------------------------ rtos消息回调处理部分 ------------------------------------------
  150. --[[
  151. 函数名:cmpTable
  152. 功能 :比较两个table的内容是否相同,注意:table中不能再包含table
  153. 参数 :
  154. t1:第一个table
  155. t2:第二个table
  156. 返回值:相同返回true,否则false
  157. ]]
  158. local function cmpTable(t1, t2)
  159. if not t2 then return #t1 == 0 end
  160. if #t1 == #t2 then
  161. for i = 1, #t1 do
  162. if unpack(t1, i, i) ~= unpack(t2, i, i) then
  163. return false
  164. end
  165. end
  166. return true
  167. end
  168. return false
  169. end
  170. --- 关闭sys.timerStart和sys.timerLoopStart创建的定时器
  171. -- 有两种方式可以唯一标识一个定时器:
  172. -- 1、定时器ID
  173. -- 2、定时器回调函数和可变参数
  174. -- @param val 有两种形式:
  175. -- 1、为number类型时,表示定时器ID
  176. -- 2、为function类型时,表示定时器回调函数
  177. -- @param ... 可变参数,当val为定时器回调函数时,此可变参数才有意义,表示定时器回调函数的可变回调参数
  178. -- @return nil
  179. -- @usage
  180. -- 通过定时器ID关闭一个定时器:
  181. -- local timerId = sys.timerStart(publicTimerCbFnc,8000,"second")
  182. -- sys.timerStop(timerId)
  183. -- 通过定时器回调函数和可变参数关闭一个定时器:
  184. -- sys.timerStart(publicTimerCbFnc,8000,"first")
  185. -- sys.timerStop(publicTimerCbFnc,"first")
  186. function timerStop(val, ...)
  187. -- val 为定时器ID
  188. local arg={ ... }
  189. if type(val) == 'number' then
  190. timerPool[val], para[val], loop[val] = nil
  191. rtos.timer_stop(val)
  192. else
  193. for k, v in pairs(timerPool) do
  194. -- 回调函数相同
  195. if type(v) == 'table' and v.cb == val or v == val then
  196. -- 可变参数相同
  197. if cmpTable(arg, para[k]) then
  198. rtos.timer_stop(k)
  199. timerPool[k], para[k], loop[val] = nil
  200. break
  201. end
  202. end
  203. end
  204. end
  205. end
  206. --- 关闭sys.timerStart和sys.timerLoopStart创建的某个回调函数的所有定时器
  207. -- @function fnc 定时器回调函数
  208. -- @return nil
  209. -- @usage
  210. -- 关闭回调函数为publicTimerCbFnc的所有定时器
  211. -- local function publicTimerCbFnc(tag)
  212. -- log.info("publicTimerCbFnc",tag)
  213. -- end
  214. --
  215. -- sys.timerStart(publicTimerCbFnc,8000,"first")
  216. -- sys.timerStart(publicTimerCbFnc,8000,"second")
  217. -- sys.timerStart(publicTimerCbFnc,8000,"third")
  218. -- sys.timerStopAll(publicTimerCbFnc)
  219. function timerStopAll(fnc)
  220. for k, v in pairs(timerPool) do
  221. if type(v) == "table" and v.cb == fnc or v == fnc then
  222. rtos.timer_stop(k)
  223. timerPool[k], para[k], loop[k] = nil
  224. end
  225. end
  226. end
  227. --- 创建并且启动一个单次定时器
  228. -- 有两种方式可以唯一标识一个定时器:
  229. -- 1、定时器ID
  230. -- 2、定时器回调函数和可变参数
  231. -- @param fnc 定时器回调函数,必须存在,不允许为nil
  232. -- 当定时器超时时间到达时,回调函数的调用形式为fnc(...),其中...为回调参数
  233. -- @number ms 定时器超时时间,单位毫秒,最小1,最大0x7FFFFFFF
  234. -- 实际上支持的最小超时时间是5毫秒,小于5毫秒的时间都会被转化为5毫秒
  235. -- @param ... 可变参数,回调函数fnc的回调参数
  236. -- @return number timerId,创建成功返回定时器ID;创建失败返回nil
  237. -- @usage
  238. -- 创建一个5秒的单次定时器,回调函数打印"timerCb",没有可变参数:
  239. -- sys.timerStart(function() log.info("timerCb") end, 5000)
  240. -- 创建一个5秒的单次定时器,回调函数打印"timerCb"和"test",可变参数为"test":
  241. -- sys.timerStart(function(tag) log.info("timerCb",tag) end, 5000, "test")
  242. function timerStart(fnc, ms, ...)
  243. --回调函数和时长检测
  244. local arg={ ... }
  245. local argcnt=0
  246. for i, v in pairs(arg) do
  247. argcnt = argcnt+1
  248. end
  249. assert(fnc ~= nil, "sys.timerStart(first param) is nil !")
  250. assert(ms > 0, "sys.timerStart(Second parameter) is <= zero !")
  251. --4G底层不支持小于5ms的定时器
  252. if ms < 5 then ms = 5 end
  253. -- 关闭完全相同的定时器
  254. if argcnt == 0 then
  255. timerStop(fnc)
  256. else
  257. timerStop(fnc, ...)
  258. end
  259. -- 为定时器申请ID,ID值 1-0X1FFFFFFF 留给任务,0X1FFFFFFF-0x7FFFFFFF留给消息专用定时器
  260. while true do
  261. if msgId >= MSG_TIMER_ID_MAX then msgId = TASK_TIMER_ID_MAX end
  262. msgId = msgId + 1
  263. if timerPool[msgId] == nil then
  264. timerPool[msgId] = fnc
  265. break
  266. end
  267. end
  268. --调用底层接口启动定时器
  269. if rtos.timer_start(msgId, ms) ~= 1 then log.debug("rtos.timer_start error") return end
  270. --如果存在可变参数,在定时器参数表中保存参数
  271. if argcnt ~= 0 then
  272. para[msgId] = arg
  273. end
  274. --返回定时器id
  275. return msgId
  276. end
  277. --- 创建并且启动一个循环定时器
  278. -- 有两种方式可以唯一标识一个定时器:
  279. -- 1、定时器ID
  280. -- 2、定时器回调函数和可变参数
  281. -- @param fnc 定时器回调函数,必须存在,不允许为nil
  282. -- 当定时器超时时间到达时,回调函数的调用形式为fnc(...),其中...为回调参数
  283. -- @number ms 定时器超时时间,单位毫秒,最小1,最大0x7FFFFFFF
  284. -- 实际上支持的最小超时时间是5毫秒,小于5毫秒的时间都会被转化为5毫秒
  285. -- @param ... 可变参数,回调函数fnc的回调参数
  286. -- @return number timerId,创建成功返回定时器ID;创建失败返回nil
  287. -- @usage
  288. -- 创建一个5秒的循环定时器,回调函数打印"timerCb",没有可变参数:
  289. -- sys.timerLoopStart(function() log.info("timerCb") end, 5000)
  290. -- 创建一个5秒的循环定时器,回调函数打印"timerCb"和"test",可变参数为"test":
  291. -- sys.timerLoopStart(function(tag) log.info("timerCb",tag) end, 5000, "test")
  292. function timerLoopStart(fnc, ms, ...)
  293. local tid = timerStart(fnc, ms, ...)
  294. if tid then loop[tid] = (ms<5 and 5 or ms) end
  295. return tid
  296. end
  297. --- 判断“通过timerStart或者timerLoopStart创建的定时器”是否处于激活状态
  298. -- @param val 定时器标识,有两种表示形式
  299. -- 1、number类型,通过timerStart或者timerLoopStart创建定时器时返回的定时器ID,此情况下,不需要传入回调参数...就能唯一标识一个定时器
  300. -- 2、function类型,通过timerStart或者timerLoopStart创建定时器时的回调函数,此情况下,如果存在回调参数,需要传入回调参数...才能唯一标识一个定时器
  301. -- @param ... 回调参数,和“通过timerStart或者timerLoopStart创建定时器”的回调参数保持一致
  302. -- @return status,定时器激活状态;根据val的表示形式,有不同的返回值:
  303. -- 1、val为number类型时:如果处于激活状态,则返回function类型的定时器回调函数;否则返回nil
  304. -- 2、val为function类型时:如果处于激活状态,则返回bool类型的true;否则返回nil
  305. -- @usage
  306. -- 定时器ID形式标识定时器的使用参考:
  307. -- local timerId1 = sys.timerStart(function() end,5000)
  308. --
  309. -- sys.taskInit(function()
  310. -- sys.wait(3000)
  311. -- log.info("after 3 senonds, timerId1 isActive?",sys.timerIsActive(timerId1))
  312. --
  313. -- sys.wait(3000)
  314. -- log.info("after 6 senonds, timerId1 isActive?",sys.timerIsActive(timerId1))
  315. -- end)
  316. --
  317. --
  318. -- 回调函数和回调参数标识定时器的使用参考:
  319. -- local function timerCbFnc2(tag)
  320. -- log.info("timerCbFnc2",tag)
  321. -- end
  322. --
  323. -- sys.timerStart(timerCbFnc2,5000,"test")
  324. --
  325. -- sys.taskInit(function()
  326. -- sys.wait(3000)
  327. -- log.info("after 3 senonds, timerCbFnc2 test isActive?",sys.timerIsActive(timerCbFnc2,"test"))
  328. --
  329. -- sys.wait(3000)
  330. -- log.info("after 6 senonds, timerCbFnc2 test isActive?",sys.timerIsActive(timerCbFnc2,"test"))
  331. -- end)
  332. function timerIsActive(val, ...)
  333. local arg={ ... }
  334. if type(val) == "number" then
  335. return timerPool[val]
  336. else
  337. for k, v in pairs(timerPool) do
  338. if v == val then
  339. if cmpTable(arg, para[k]) then return true end
  340. end
  341. end
  342. end
  343. end
  344. ------------------------------------------ LUA应用消息订阅/发布接口 ------------------------------------------
  345. -- 订阅者列表
  346. local subscribers = {}
  347. --内部消息队列
  348. local messageQueue = {}
  349. --- 订阅消息
  350. -- @param id 消息id
  351. -- @param callback 消息回调处理
  352. -- @usage subscribe("NET_STATUS_IND", callback)
  353. function subscribe(id, callback)
  354. if type(id) ~= "string" or (type(callback) ~= "function" and type(callback) ~= "thread") then
  355. log.warn("warning: sys.subscribe invalid parameter", id, callback)
  356. return
  357. end
  358. if not subscribers[id] then subscribers[id] = {count = 0} end
  359. if not subscribers[id][callback] then
  360. subscribers[id].count = subscribers[id].count + 1
  361. subscribers[id][callback] = true
  362. end
  363. end
  364. --- 取消订阅消息
  365. -- @param id 消息id
  366. -- @param callback 消息回调处理
  367. -- @usage unsubscribe("NET_STATUS_IND", callback)
  368. function unsubscribe(id, callback)
  369. if type(id) ~= "string" or (type(callback) ~= "function" and type(callback) ~= "thread") then
  370. log.warn("warning: sys.unsubscribe invalid parameter", id, callback)
  371. return
  372. end
  373. -- 取消订阅时将对应取消的函数赋值为false,不能直接赋值为nil,否则可能触发lua invalid key to 'next'异常
  374. if subscribers[id] then
  375. if subscribers[id][callback] then
  376. subscribers[id].count = subscribers[id].count - 1
  377. subscribers[id][callback] = false
  378. end
  379. end
  380. end
  381. --- 发布内部消息,存储在内部消息队列中
  382. -- @param ... 可变参数,用户自定义
  383. -- @return 无
  384. -- @usage publish("NET_STATUS_IND")
  385. function publish(...)
  386. local arg = { ... }
  387. table.insert(messageQueue, arg)
  388. end
  389. -- 分发消息
  390. local function dispatch()
  391. while true do
  392. if #messageQueue == 0 then
  393. -- 当在同一个task内sys.waitUntil()不同的消息,并且没有任何消息publish时,会造成内存泄漏。
  394. -- 例如:sys.waitUntil("1", 500)、sys.waitUntil("2", 500)、...、sys.waitUntil("n", 500)
  395. for k, v in pairs(subscribers) do
  396. if v.count == 0 then subscribers[k] = nil end
  397. end
  398. break
  399. end
  400. local message = table.remove(messageQueue, 1)
  401. if subscribers[message[1]] then
  402. for callback, flag in pairs(subscribers[message[1]]) do
  403. if flag then
  404. if type(callback) == "function" then
  405. callback(unpack(message, 2, #message))
  406. elseif type(callback) == "thread" then
  407. coroutine.resume(callback, unpack(message))
  408. end
  409. end
  410. end
  411. -- 当某个subscribe的消息的回调取消订阅时,在这里将它赋值为nil,回收内存
  412. if subscribers[message[1]] then
  413. for callback, flag in pairs(subscribers[message[1]]) do
  414. if not flag then
  415. subscribers[message[1]][callback] = nil
  416. end
  417. end
  418. --当所有subscribe消息的回调都取消订阅时,在这里清空对应消息的表,回收内存
  419. if subscribers[message[1]].count == 0 then
  420. subscribers[message[1]] = nil
  421. end
  422. end
  423. end
  424. end
  425. end
  426. -- rtos消息回调
  427. local handlers = {}
  428. setmetatable(handlers, {__index = function() return function() end end, })
  429. --- 注册rtos消息回调处理函数
  430. -- @number id 消息类型id
  431. -- @param handler 消息处理函数
  432. -- @return 无
  433. -- @usage rtos.on(rtos.MSG_KEYPAD, function(param) handle keypad message end)
  434. rtos.on = function(id, handler)
  435. handlers[id] = handler
  436. end
  437. ------------------------------------------ Luat 主调度框架 ------------------------------------------
  438. --- run()从底层获取core消息并及时处理相关消息,查询定时器并调度各注册成功的任务线程运行和挂起
  439. -- @return 无
  440. -- @usage sys.run()
  441. function run()
  442. while true do
  443. -- 分发内部消息
  444. dispatch()
  445. -- 阻塞读取外部消息
  446. local msg, param = rtos.receive(rtos.INF_TIMEOUT)
  447. -- 判断是否为定时器消息,并且消息是否注册
  448. if msg == rtos.MSG_TIMER and timerPool[param] then
  449. if param <= TASK_TIMER_ID_MAX then
  450. local taskId = timerPool[param]
  451. timerPool[param] = nil
  452. if taskTimerPool[taskId] == param then
  453. taskTimerPool[taskId] = nil
  454. coroutine.resume(taskId)
  455. end
  456. else
  457. local cb = timerPool[param]
  458. --如果不是循环定时器,从定时器id表中删除此定时器
  459. if not loop[param] then timerPool[param] = nil end
  460. if para[param] ~= nil then
  461. cb(unpack(para[param]))
  462. if not loop[param] then para[param] = nil end
  463. else
  464. cb()
  465. end
  466. --如果是循环定时器,继续启动此定时器
  467. if loop[param] then rtos.timer_start(param, loop[param]) end
  468. end
  469. --其他消息(音频消息、充电管理消息、按键消息等)
  470. elseif type(msg) == "number" then
  471. handlers[msg](param)
  472. else
  473. handlers[msg.id](msg)
  474. end
  475. end
  476. end
  477. require "clib"
  478. if type(rtos.openSoftDog)=="function" then
  479. rtos.openSoftDog(60000)
  480. sys.timerLoopStart(rtos.eatSoftDog,20000)
  481. end