定时让用户可以规划机器人哪时间执行哪任务,像6:30打开豆浆机,17:00关窗帘。
- 任务总是归属哪个小程序。小程序中被调函数都是lua脚本函数,即实现任务的小程序可以全是lua代码。
- 一个小程序能包含多个任务。
- 一天内可以多次执行同一任务。
- 每个任务会有个触发时间t,任务会在[t, t+5分钟]内的某个时间点开始执行。为什么不是准点t?一来app不能保证恰好在t时刻检测开始哪任务,二来在t时刻因为一些原因该任务可能无法执行。
- 小程序优先级比任务高。这表现在两个方面。1)当前有任务正在执行,一旦用户运行任务所在小程序,或和该任务有权限冲突的小程序,任务会立即被结束。2)该任务所在小程序正在运行,或和该任务有权限冲突的小程序正在运行,虽然时间点上该任务是可执行了,但也得等。如果(t+5分钟)内用户结束小程序,那会开始该任务。
- 引入权限,是因为有独占资源,目前两种权限:蓝牙、导航。和手机编程的权限不同,这里权限总是被允许的,又要写上,是为保证把这独占资源赋给优先级更高的使用者。举个例子,正运行的任务A需要蓝牙,用户此刻要运行小程序b,小程序b也需要蓝牙,那只能结束掉优先级低的任务A。
- 任务执行时,不能主动在界面弹出什么提示,即任务不能有界面操作。要想告知错误、重要提示,等等,通过写timing.pb日志。
- 需要蓝牙权限时,考虑到蓝牙会出现偶然错误,第一次执行失败后,如果时间没过[t, t+5分钟],会给第二次机会。
标识 | 描述 |
navigation | 导航 |
ble | 蓝牙 |
一、编写任务
以下用了“BLE Smart”小程序中代码,其bundleid是aplt.leagor.blesmart,任务id是query_ip。
运行任务时,app只加载两个lua文件:main.lua、timing.lua。默认必须存在两个表:aplt_leagor_blesmart、aplt_leagor_blesmart__timing,后者是任务特有,要没意外,写在timing.lua。
1.1 settings.cfg
[timing] id = "query_ip" permissions = "navigation, ble" [/timing]
通过写在settings.cfg的[timing]块,小程序向外告知它含有哪些任务。一个[timing]表示一个任务,可以出现多个。
在[timing]块内,id用于标识任务,不同小程序可重复,此小程序内唯一就行。permissions表示该任务要使用的权限。
1.2 aplt_leagor_blesmart__timing.start(id)
到点了,app要执行某个任务,就会调用xxx__timing.start。参数id(string)指示要执行的任务,像quiery_ip。
function aplt_leagor_blesmart__timing.start(id) local aplt = aplt_leagor_blesmart; local this = aplt_leagor_blesmart__timing; if id == "query_ip" then aplt.create_ble("timing"); this.start_scan(); return true, 500; else return false, "unknown timing id:" .. id; end end
启动任务。只是启动,并不是一定要在这函数内执行完任务。在示例,创建一个vble对象,并开始蓝牙扫描。
填两个返回值,这分两种情况。
- 任务能执行。第一个true(bool),表示成功。第二个是定时器时间间隔(integer),单位毫秒,最小400毫秒。0表示不用定时器。示例想使用一个500毫秒定时器。
- 任务不能执行。第一个false(bool),表示失败。第二个是错误描述(string)。
一旦start成功,进入任务执行阶段。
1.3 aplt_leagor_blesmart__timing.timer_handler(now)
如果在start时返回了非0的定时器间隔,到时间会调用xxx__timing.timer_handler。参数now(integer)等于此刻用rose.SDL_GetTicks()计算出的值。
function aplt_leagor_blesmart__timing.timer_handler(now) local aplt = aplt_leagor_blesmart; local this = aplt_leagor_blesmart__timing; local _ = aplt_leagor_blesmart._ local vgettext2 = aplt_leagor_blesmart.vgettext2; rose.log(now .. " aplt_leagor_blesmart__timing.timer_handler called"); if this.stop_scan_ticks_ ~= 0 then assert(this.scaning_); if now >= this.stop_scan_ticks_ then this.stop_scan(); local max_times = 2; if this.scan_times_ <= max_times then rose.timing_log(vgettext2("$times|th scan fail, try next scan", {times = this.scan_times_})); this.start_scan(); else rose.timing_finished(_("No scan to device, task failed")); end end elseif this.connect_ticks_ ~= 0 then local threshold = 5000; -- 5 second if now >= this.connect_ticks_ + threshold then rose.timing_finished(_("Connected, but can not complete task within a limited time")); end end end
示例用定时器控制扫描溢出时间。另外,如果连接成功后,5秒内不能完成查询ip,认为任务执行失败。
1.4 rose.timing_log(log)
输出任务日志,log(string)是日志内容
任务在后台无声执行,但用户得有地方知道今天设定的定时任务是什么个执行情况。作为中介,系统使用一个timing.pb文件,任务执行过程中,调用rose.timing_log向timing.pb写日志,用户则通过查看timing.pb,知道任务执行结果。
1.5 rose.timing_finished(log)
调用rose.timing_finished,小程序告知app,完成任务了。考虑到完成时往往会输出一条日志,这里就给个log(string)参数,免得额外调用rose.timing_log。
1.6 aplt_leagor_blesmart__timing.stop()
app发现ttiming.running_.finished是true,会调用xxx__timing.stop。在这里,小程序把此次任务是成功还是失败反馈给app。
function aplt_leagor_blesmart__timing.stop() local aplt = aplt_leagor_blesmart; local this = aplt_leagor_blesmart__timing; if this.my_peripheral_ ~= nil then aplt.ble_:disconnect_peripheral(); end return this.result; end
任务只要进入执行阶段了,那xxx__timing.stop一定会被调用,而且是此次执行的最后一个函数。在这里可以放一些释放操作。示例是断开连接。
填写bool返回值,true表示此次任务执行成功、false则失败。示例中this.reult是个bool变量,初始false,查到一个ip后,置为true,于是它就指示了此次任务的执行成败。
如果任务需要一条日志来描述执行结果,那处理方法是在之前、以它为传参数调用rose.timing_finished。