Skip to content

带恢复的穿越目标位姿导航行为树

该行为树实现了一种导航行为:从起始点出发,依次穿过多个中间硬位姿约束(hard pose constraints),最终到达自由空间中的目标点。它在特定场景中使用了自定义恢复行为,同时也包含用于系统级故障的全局恢复子树。此外,在最终返回失败之前,它会多次重试整个导航任务。

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

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

正常运行时,如果距离目标不够近,它会每 3 秒重新规划一次路径并将路径传递给控制器,这一点与 行为树 中的行为树类似。不同之处在于,此处规划器为 ComputePathThroughPoses,它接收一个目标列表 goals 而非单个位姿 goal 来进行规划。RemovePassedGoals 节点用于剔除机器人已经经过的航点。在本例中,当机器人距离某个航点 0.5 以内、且该航点是列表中的下一个目标时,就会将其从列表中移除。此外,该节点还会在 waypoint_statuses 中记录每个航点的状态(例如 PENDING、COMPLETED、SKIPPED 或 FAILED)。这样设计使得机器人在经过部分中间航点后可以重新规划路径,而无需继续规划已通过的航点。

如果规划器失败,会在其子树中触发场景化恢复行为,清除全局代价地图。也可以在此处添加其他局部恢复行为,例如尝试换用另一种算法。

控制器也有类似的逻辑:如果控制器失败,会尝试清除局部代价地图。这里需要关注 ReactiveFallback(反应式回退)节点中的 GoalUpdated 节点。当导航系统通过抢占(preemption)收到新目标时,该节点可以让我们跳出恢复流程。这样即使上一个目标正处于恢复尝试中,导航系统也能在收到新目标后立即响应。

如果这些场景化恢复仍然失败,行为树将进入恢复子树。该子树专门用于处理系统级故障,帮助解决诸如机器人被困或处于不利位置等问题。子树中也包含 GoalUpdated 行为树节点,每次迭代都会触发,以确保对新目标快速响应。随后,恢复子树将依次执行代价地图清除、原地旋转、等待和后退等恢复动作。每次恢复动作执行完毕后,都会重新尝试主导航子树。如果仍然失败,则触发恢复子树中的下一个恢复动作。

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

<root BTCPP_format="4" main_tree_to_execute="NavigateThroughPosesWReplanningAndRecovery">
<BehaviorTree ID="NavigateThroughPosesWReplanningAndRecovery">
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
<PipelineSequence name="NavigateWithReplanning">
<ProgressCheckerSelector selected_progress_checker="{selected_progress_checker}" default_progress_checker="progress_checker" topic_name="progress_checker_selector"/>
<GoalCheckerSelector selected_goal_checker="{selected_goal_checker}" default_goal_checker="general_goal_checker" topic_name="goal_checker_selector"/>
<PathHandlerSelector selected_path_handler="{selected_path_handler}" default_path_handler="PathHandler" topic_name="path_handler_selector"/>
<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="0.333">
<RecoveryNode number_of_retries="1" name="ComputePathThroughPoses">
<Fallback name="FallbackComputePathToPose">
<ReactiveSequence name="CheckIfNewPathNeeded">
<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>
<ReactiveSequence>
<RemovePassedGoals input_goals="{goals}" output_goals="{goals}" radius="0.7" input_waypoint_statuses="{waypoint_statuses}" output_waypoint_statuses="{waypoint_statuses}"/>
<ComputePathThroughPoses goals="{goals}" path="{path}" planner_id="{selected_planner}" error_code_id="{compute_path_error_code}" error_msg="{compute_path_error_msg}"/>
</ReactiveSequence>
</Fallback>
<Sequence>
<WouldAPlannerRecoveryHelp error_code="{compute_path_error_code}"/>
<ClearEntireCostmap name="ClearGlobalCostmap-Context" service_name="global_costmap/clear_entirely_global_costmap"/>
</Sequence>
</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}" goal_checker_id="{selected_goal_checker}" progress_checker_id="{selected_progress_checker}" path_handler_id="{selected_path_handler}" tracking_feedback="{tracking_feedback}"/>
<Sequence>
<WouldAControllerRecoveryHelp error_code="{follow_path_error_code}"/>
<ClearEntireCostmap name="ClearLocalCostmap-Context" service_name="local_costmap/clear_entirely_local_costmap"/>
</Sequence>
</RecoveryNode>
</PipelineSequence>
<Sequence>
<Fallback>
<WouldAControllerRecoveryHelp error_code="{follow_path_error_code}"/>
<WouldAPlannerRecoveryHelp error_code="{compute_path_error_code}"/>
</Fallback>
<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>
</Sequence>
</RecoveryNode>
</BehaviorTree>
</root>