- 有条件地允许一个前台和一个后台任务同时运行,条件是不存在权限冲突。但不会同时运行两个后台任务。
- 后台任务分为三类优先级:首先是在定时任务前执行的系统任务,然后是定时任务,最后是定时任务后执行的系统任务。
- 注意区分timing任务和定时任务,timing虽然意思是定时,但timing任务不仅仅用于定时任务,还用于type_aplt_task类型的临时任务。它定义在小程序settings.cfg的timing块,所以称为timing任务。
- 执行后台任务时,一旦要运行小程序,内中libroseaplt.so是以driver方式加载,加载类型apltsotype_bgaplt,字面“bg”是background的缩写。另外,直接运行小程序,即前台任务时,加载类型是apltsotype_curraplt。
- 任务都会绑定一个小程序,这包括任何一种类型的后台任务。instance->current_aplt_指向前台任务绑定的小程序,timing_.aplt_则指向后台任务绑定的小程序。
- 后台任务绑定的小程序不可能是前台任务小程序。换句话说,当有前台任务在运行时,禁止运行以它为小程序的后台任务。
- 后台任务限制:不能弹出界面。有重要信息放到日志,或声音。
除直接运行小程序外,其它的都可归为后台任务。直接运行小程序是指在桌面点击小程图标,然后小程序被启动,代码入口是lua中的[aplt].start()。
后台任务分为两类,定时任务和系统任务。
- 定时任务。由用户预先在定时模块添加,写在一个定时任务列表中。
- 系统任务。非定时列表触发,触发方式像语音,中心发命令,检测到快没电了,等等。首先分为临时任务和自动回充。临时任务又分为“去[位置][timing任务](type_aplt_task)”,“去[位置](type_moveto)”,“识别图像(type_recognition)”,“去充电(type_charge)”。临时任务中去充电是收到命令后,不管电量多少,立即去充电;自动回充是机器人检测到电量低了,自动回去充电。
定时任务,以及系统任务中的type_aplt_task,它们执行的任务是同一种,是某个小程序向外提供的任务。它定义在settings.cfg的timing块中,也称为timing任务。注意,timing虽然意思是定时,但timing任务不仅仅用于定时任务。既然是在小程序,这就涉及到如何加载该小程序libroseaplt.so。它是以着driver加入,类型是apltsotype_bgaplt。
存在5种类型driver,其中apltsotype_curraplt, apltsotype_bgaplt不是表示驱动,但还是drivers管理着。
- apltsotype_curraplt。直接运行的小程序,即前台任务。
- apltsotype_bgaplt。后台任务中的小程序。后台任务中任何类型都会绑定一个小程序,像自动回充,它会绑定提供底盘驱动的小程序。
不论定时任务,还是type_aplt_task,执行的都是timing任务,在代码中以着同一个函数去启动(start_aplt_task)和结束(stop_aplt_task)。
后台任务限制:不能弹出界面。只是简单弹个提示框,都是不允许的。有重要信息放在日志,或声音。
一、抢占、优先级
任一时刻,要有条件地允许一个前台和一个后台任务同时运行。举个例子,前台任务是一个聊天小程序,只是用到大模型,不涉及导航、蓝牙权限。那要允许执行去窗台打开窗帘的定时任务。上面也说,这是有条件的,条件是不允许存在冲突权限。
前台任务有着最高优先级。直接启动小程序时,要是有正运行着的后台任务,且存在权限冲突,会先结束后台任务。
同是后台任务也存在抢占。具体表现在执行一个临时任务时,要是有后台任务正在运行,会先结束掉那个后台任务。这时不管有没有存在权限冲突,因为任何时刻只能有一个后台任务。
启动前台任务是执行applet::texecutor::run(),这逻辑较简单,相比来说,启动后台任务要复杂些,需要ttiming::shedule进行调度。
1.1 ttiming::shedule
ttiming::shedule()是调度后台任务的时间片函数,它在app时间片内被调用。它决定了三种后台任务的优化级。
<librose>/aplt.cpp ------ void ttiming::shedule() { VALIDATE(aplt_ == nullptr, null_str);
aplt_指向绑定到正运行后台任务的小程序,如果非nullptr,意味着有正运行的后台任务,禁止进入此函数。
...... { applet::ttiming::tsys_task* sys_task = instance->timing_sys_shedule(true); if (sys_task != nullptr) { start_sys(*sys_task); return;
找到了一个需在定时任务前执行的系统任务,目前就临时任务,执行它。
} } ...... // 一旦找到了现在要执行的定时任务,hit_task则指向这个定时任务。 if (hit_task == nullptr) { applet::ttiming::tsys_task* sys_task = instance->timing_sys_shedule(false); if (sys_task != nullptr) { start_sys(*sys_task);
找到了一个优先级比定时任务低,需执行的系统任务,目前就自动回充,执行它。
} return; } ...... start_aplt_task(*hit_aplt, *new_task, nullptr);
这些代码是执行hit_task这个定时任务。
}
后台任务分为三类优先级:首先是在定时任务前执行的系统任务,然后是定时任务,最后是定时任务后执行的系统任务。
具体到任务上,语音、中心发命令触发的临时任务,是即时产生,需要立即响应,因而放时定时任务之前。临时任务中的“临时”就有要立即执行意思。对自动回充,毕竟不是不充电就立即不能用了,于是放在定时任务之后。
二、base_instance::timing_sys_shedule
timing_sys_shedule用于调度某个系统任务去执行,参数pre指示此时是在定时任务前,还是在定时任务后。
/base_instance.cpp ------ applet::ttiming::tsys_task* base_instance::timing_sys_shedule(bool pre) { bool disallowed = false; if (applet::tdisable_new_aplt_lock::disabled()) { disallowed = true;
tdisable_new_aplt_lock::disabled()是true,表示当前禁止执行后台任务。
} else { const applet::tapplet* running_aplt = instance->current_aplt(); if (running_aplt != nullptr) { if (running_aplt->permissions.count(applet::PERMISSION_ID_NAVIGATION)) { disallowed = true; }
(需完善1)running_aplt指向前台任务小程序,认为后台任务总是需要导航权限,于是为简单,只要前台任务有导航权限,就认为不能运行后台任务。
} } if (disallowed) { app_sys_timing_disallowed(pre);
当前不允许执行后台任务。这个后台任务有可能是语音触发的临时任务,说话人正等着,为让知道为什么没执行,在app_sys_timing_disallowed给个语音提示啥的。
return nullptr; } return app_timing_sys_shedule(pre);
app_timing_sys_shedule返回需运行的系统任务。检查和当前任务的权限是否有冲突是放在系统任务的shedule,这里如果返回不是nullptr,权限肯定没冲突。
}
tdisable_new_aplt_lock::disabled()是true,表示当前禁止执行后台任务。同时它也会禁止执行定时任务,代码在schedule()。以下是几种会禁止情况。
enum | 描述 |
reason_map | 正在显示“地图”窗口。可能会修改当前地图信息 |
reason_settings | 正在显示“设置”窗口。可能会修改驱动要使用哪个小程序 |
reason_dnn | 正在显示“DNN”窗口 |
reason_moveit | 正在显示“机械臂”窗口 |
reason_netxmit | 正在用ihttp_cswamp,包括下载小程序,升级launcher、拼音包等 |
reason_moveit计划留留给机械臂,当前未使用。
三、tros_instance::ttask
ttask让当前环境具有ros操作能力。具体说,会确保存在能获得tf树的tf2_ros::Buffer,能让回调到接收导航结果的did_move_base_result,等等。和ros相关的两个主要操作是导航和机械臂,在它们执行期间,须确保系统中至少存在一个ttask。
导航、机械臂,往往是后台任务中做的一部分操作,尤其导航,好多后台任务的第一阶段就是它。导航会创建ttask,不免就会把ttask和后台任务联系在一起。但无论无何,ttask功能是让当前环境具有ros操作能力。如果一个前台任务须要导航、机械臂,照样会创建ttask,后面“lua代码中导航”会说到这个。
开始导航阶段的入口函数是base_instance::lua_did_navigation_start,在执行这函数期间,会创建ttask。如果之前有一个,会先销毁,再重新创建。
因为存在lua_did_navigation_start时创建的ttask,即使地图界面这些都销毁了,还有它在确保证能执行ros操作。那什么时候销毁ttask。
- (后台任务或前台小程序)导航结束,app会收到did_move_base_result。如果luafunc指定的任务不是机械臂,是timing任务、lua函数这些,会erase_task。为什么机械臂时不销毁task?——操作机械臂须要ros环境,免得重新创建task。
- (后台任务)如果luafunc指定任务是机械臂,那要等到操作完机械臂,结束此个后台任务,执行app_post_stop_bg_task()时。另外,如果提前中止后台任务中导航,也是在这里erase_task。
- (前台小程序)想提前中止导航,方法是关闭当前窗口,关闭窗口(APLT_MSG_DIDDLGCLOSE)时会erase_task。退出小程序一定会关闭窗口,所以退出也能中止导航。
四、lua代码中导航
前台小程序会须到导航,形式是“导航+lua代码”。让想像下空调小程序,要求把空调调到25度。那它执行步骤是先移到空调位置,再调25度温。为可灵活设置温度值,调温不是作为一个timing任务,而是段lua代码。
1、(lua,小程序)navigation_goal_:set_did_left_click("click_navigation_goal");
set_did_left_click用于挂接当用户单击navigation_goal_按钮后,会执行什么操作。一旦单击按钮,将执行个lua函数:click_navigation_goal。
2、(lua,小程序)widget:navigation_start(uuid, "did_navigation_goal");
在click_navigation_goal中执行这条语句,也就是说,当用户单击navigation_goal_按钮后,会执行它。
widget是一个从tcontrol派生的控件类型,像此刻正单击的按钮。参数uuid是要移动到位置的uuid。did_navigation_goal是导航结束后要执行的lua函数。
navigation_start是个C实现函数,它会调用lua_did_navigation_start启动导航,后者会调用ros_instance_.moveto_guid。
导航结束,会调用did_move_base_result。在那里会调用lua代码写的did_navigation_goal。并会erase_task。如果想提前中止导航,那关闭当前窗口,或退出小程序。这整个过程有创建、销毁ttask,但没有后台任务。