Skip to content

持续重规划与路径失效处理行为树

该行为树是 行为树 所介绍版本的进阶实现,用于从起点导航到自由空间中的某个目标点。它包含针对特定场景的局部恢复行为,以及用于系统级故障的全局恢复子树,并在最终返回失败前提供多次重试机会。

ComputePathToPose 和 FollowPath 行为树节点也都指定了要使用的算法。按照惯例,节点以功能风格而非具体算法命名(例如使用 FollowPath 而非 DWB),这样行为树或应用开发者无需关注底层技术细节,只需获得一个路径跟随控制器即可。

在最终返回失败前,该行为树会尝试重新执行整个导航任务 6 次,为系统提供充足的恢复机会,或等待暂时性问题(如人群拥挤、传感器临时故障)自行消除。

正常运行时,重规划可由以下情况触发:之前的路径失效、收到新目标,或 10 秒内未生成新路径。如果规划器或控制器失败,会在各自的子树中触发场景化恢复行为。目前,规划器失败时会清除全局代价地图,控制器失败时则清除局部代价地图,也可以根据需要向这些子树添加更多恢复行为。

如果场景化恢复仍不奏效,行为树将进入恢复子树。该子树专门处理系统级故障,例如机器人被困或处于不利位置等。子树中包含 GoalUpdated 行为树节点,每次迭代都会触发,确保对新目标快速响应。接下来,恢复子树依次执行以下恢复行为:代价地图清除、原地旋转、等待和后退。每执行一次恢复后,都会重新尝试主导航子树;若仍然失败,则触发恢复子树中的下一个恢复行为。

该行为树虽然未使用 PlannerSelector、ControllerSelector 和 GoalCheckerSelector 等选择器节点,但这些节点在实际应用中非常有用。它们不再硬编码算法(如 GridBased 和 FollowPath),而是允许通过 ROS 话题动态切换导航算法。另一种更推荐的做法是:在特定场景下利用条件节点指定算法,创建不同的子树分支。不过,选择器节点为外部应用(而非行为树内部控制流逻辑)提供了切换算法的便捷途径。虽然通过行为树内部控制流实现算法切换更为理想,但许多专业用户拥有自己的外部应用来动态调整导航器设置,选择器节点正好满足这一需求。

<root BTCPP_format="4" main_tree_to_execute="NavToPoseWithConsistentReplanningAndIfPathBecomesInvalid">
<BehaviorTree ID="NavToPoseWithConsistentReplanningAndIfPathBecomesInvalid">
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
<PipelineSequence name="NavigateWithReplanning">
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>
<PlannerSelector selected_planner="{selected_planner}" default_planner="GridBased" topic_name="planner_selector"/>
<RateController hz="2.0">
<RecoveryNode number_of_retries="1" name="ComputePathToPose">
<Fallback>
<ReactiveSequence>
<Inverter>
<PathExpiringTimer seconds="10" path="{path}"/>
</Inverter>
<Inverter>
<GlobalUpdatedGoal/>
</Inverter>
<IsGoalNearby path="{path}" proximity_threshold="4.0" max_robot_pose_search_dist="1.5"/>
<TruncatePathLocal input_path="{path}" output_path="{remaining_path}" distance_forward="-1" distance_backward="0.0" />
<ValidatePath path="{remaining_path}"/>
</ReactiveSequence>
<ComputePathToPose goal="{goal}" path="{path}" planner_id="{selected_planner}" error_code_id="{compute_path_error_code}" error_msg="{compute_path_error_msg}"/>
</Fallback>
<ClearEntireCostmap name="ClearGlobalCostmap-Context" service_name="global_costmap/clear_entirely_global_costmap"/>
</RecoveryNode>
</RateController>
<RecoveryNode number_of_retries="1" name="FollowPath">
<FollowPath path="{path}" controller_id="{selected_controller}" error_code_id="{follow_path_error_code}" error_msg="{follow_path_error_msg}" tracking_feedback="{tracking_feedback}"/>
<ClearEntireCostmap name="ClearLocalCostmap-Context" service_name="local_costmap/clear_entirely_local_costmap"/>
</RecoveryNode>
</PipelineSequence>
<ReactiveFallback name="RecoveryFallback">
<GoalUpdated/>
<RoundRobin name="RecoveryActions">
<Sequence name="ClearingActions">
<ClearEntireCostmap name="ClearLocalCostmap-Subtree" service_name="local_costmap/clear_entirely_local_costmap"/>
<ClearEntireCostmap name="ClearGlobalCostmap-Subtree" service_name="global_costmap/clear_entirely_global_costmap"/>
</Sequence>
<Spin name="SpinRecovery" spin_dist="1.57" error_code_id="{spin_error_code}" error_msg="{spin_error_msg}"/>
<Wait name="WaitRecovery" wait_duration="5.0" error_code_id="{wait_error_code}" error_msg="{wait_error_msg}"/>
<BackUp name="BackUpRecovery" backup_dist="0.30" backup_speed="0.15" error_code_id="{backup_error_code}" error_msg="{backup_error_msg}"/>
</RoundRobin>
</ReactiveFallback>
</RecoveryNode>
</BehaviorTree>
</root>