diff --git a/nav2_bt_navigator/src/bt_navigator.cpp b/nav2_bt_navigator/src/bt_navigator.cpp index 7298b874b0591c2b577d70f79eaae849f6b3f0bc..5db5700b8bb1fb9bd07369a9d8f99ac422df0310 100644 --- a/nav2_bt_navigator/src/bt_navigator.cpp +++ b/nav2_bt_navigator/src/bt_navigator.cpp @@ -149,18 +149,28 @@ BtNavigator::on_cleanup(const rclcpp_lifecycle::State & /*state*/) { RCLCPP_INFO(get_logger(), "Cleaning up"); + // TODO(orduno) Fix the race condition between the worker thread ticking the tree + // and the main thread resetting the resources, see #1344 + goal_sub_.reset(); client_node_.reset(); self_client_.reset(); - tf_.reset(); + + // Reset the listener before the buffer tf_listener_.reset(); + tf_.reset(); + action_server_.reset(); plugin_lib_names_.clear(); xml_string_.clear(); + + RCLCPP_INFO(get_logger(), "Cleaning tree"); + tree_.reset(); blackboard_.reset(); bt_.reset(); + RCLCPP_INFO(get_logger(), "Completed Cleaning up"); return nav2_util::CallbackReturn::SUCCESS; } diff --git a/nav2_lifecycle_manager/src/lifecycle_manager.cpp b/nav2_lifecycle_manager/src/lifecycle_manager.cpp index 08c054a840b0200cf45482f37125840d305b2265..6b24850436e02d6a8a071b4d7f5f00caf6b0fd35 100644 --- a/nav2_lifecycle_manager/src/lifecycle_manager.cpp +++ b/nav2_lifecycle_manager/src/lifecycle_manager.cpp @@ -212,8 +212,9 @@ bool LifecycleManager::reset() { message("Resetting managed nodes..."); - if (!changeStateForAllNodes(Transition::TRANSITION_DEACTIVATE) || - !changeStateForAllNodes(Transition::TRANSITION_CLEANUP)) + // Should transition in reverse order + if (!changeStateForAllNodes(Transition::TRANSITION_DEACTIVATE, true) || + !changeStateForAllNodes(Transition::TRANSITION_CLEANUP, true)) { RCLCPP_ERROR(get_logger(), "Failed to reset nodes: aborting reset"); return false; diff --git a/nav2_system_tests/src/system/tester_node.py b/nav2_system_tests/src/system/tester_node.py index 691da0feb20c8145b6f0eb13eeb3b06cf755e7b8..2b0610b364708a96e0c2e3a985e9afbaf9a8d9e1 100755 --- a/nav2_system_tests/src/system/tester_node.py +++ b/nav2_system_tests/src/system/tester_node.py @@ -19,13 +19,17 @@ import sys import time from typing import Optional +from action_msgs.msg import GoalStatus from geometry_msgs.msg import Pose from geometry_msgs.msg import PoseStamped from geometry_msgs.msg import PoseWithCovarianceStamped from lifecycle_msgs.srv import GetState +from nav2_msgs.action import NavigateToPose +from nav2_msgs.srv import ManageLifecycleNodes import rclpy +from rclpy.action import ActionClient from rclpy.node import Node from rclpy.qos import QoSDurabilityPolicy, QoSHistoryPolicy, QoSReliabilityPolicy from rclpy.qos import QoSProfile @@ -55,6 +59,7 @@ class NavTester(Node): self.initial_pose_received = False self.initial_pose = initial_pose self.goal_pose = goal_pose + self.action_client = ActionClient(self, NavigateToPose, 'NavigateToPose') def info_msg(self, msg: str): self.get_logger().info('\033[1;37;44m' + msg + '\033[0m') @@ -73,12 +78,48 @@ class NavTester(Node): self.initial_pose_pub.publish(msg) self.currentPose = self.initial_pose - def setGoalPose(self, pose: Optional[Pose] = None): - self.goal_pose = pose if pose is not None else self.goal_pose + def getStampedPoseMsg(self, pose: Pose): msg = PoseStamped() msg.header.frame_id = 'map' - msg.pose = self.goal_pose - self.goal_pub.publish(msg) + msg.pose = pose + return msg + + def publishGoalPose(self, goal_pose: Optional[Pose] = None): + self.goal_pose = goal_pose if goal_pose is not None else self.goal_pose + self.goal_pub.publish(self.getStampedPoseMsg(self.goal_pose)) + + def runNavigateAction(self, goal_pose: Optional[Pose] = None): + # Sends a `NavToPose` action request and waits for completion + self.info_msg("Waiting for 'NavigateToPose' action server") + while not self.action_client.wait_for_server(timeout_sec=1.0): + self.info_msg("'NavigateToPose' action server not available, waiting...") + + self.goal_pose = goal_pose if goal_pose is not None else self.goal_pose + goal_msg = NavigateToPose.Goal() + goal_msg.pose = self.getStampedPoseMsg(self.goal_pose) + + self.info_msg('Sending goal request...') + send_goal_future = self.action_client.send_goal_async(goal_msg) + + rclpy.spin_until_future_complete(self, send_goal_future) + goal_handle = send_goal_future.result() + + if not goal_handle.accepted: + self.error_msg('Goal rejected') + return False + + self.info_msg('Goal accepted') + get_result_future = goal_handle.get_result_async() + + self.info_msg("Waiting for 'NavigateToPose' action to complete") + rclpy.spin_until_future_complete(self, get_result_future) + status = get_result_future.result().status + if status != GoalStatus.STATUS_SUCCEEDED: + self.info_msg('Goal failed with status code: {0}'.format(status)) + return False + + self.info_msg('Goal succeeded!') + return True def poseCallback(self, msg): self.info_msg('Received amcl_pose') @@ -127,6 +168,22 @@ class NavTester(Node): self.error_msg('Exception while calling service: %r' % future.exception()) time.sleep(5) + def shutdown(self): + self.info_msg('Shutting down') + transition_service = 'lifecycle_manager/manage_nodes' + mgr_client = self.create_client(ManageLifecycleNodes, transition_service) + while not mgr_client.wait_for_service(timeout_sec=1.0): + self.info_msg(transition_service + ' service not available, waiting...') + + req = ManageLifecycleNodes.Request() + req.command = ManageLifecycleNodes.Request().SHUTDOWN + future = mgr_client.call_async(req) + rclpy.spin_until_future_complete(self, future) + try: + future.result() + except Exception as e: + self.error_msg('Service call failed %r' % (e,)) + def test_InitialPose(robot_tester, timeout, retries): robot_tester.initial_pose_received = False @@ -147,7 +204,7 @@ def test_InitialPose(robot_tester, timeout, retries): def test_RobotMovesToGoal(robot_tester): robot_tester.info_msg('Setting goal pose') - robot_tester.setGoalPose() + robot_tester.publishGoalPose() robot_tester.info_msg('Waiting 60 seconds for robot to reach goal') return robot_tester.reachesGoal(timeout=60, distance=0.5) @@ -161,7 +218,14 @@ def run_all_tests(robot_tester): if (result): robot_tester.wait_for_node_active('bt_navigator') if (result): - result = test_RobotMovesToGoal(robot_tester) + result = robot_tester.runNavigateAction() + + # TODO(orduno) Test sending the navigation request through the topic interface. + # Need to update tester to receive multiple goal poses. + # Need to fix bug with shutting down while bt_navigator + # is still running. + # if (result): + # result = test_RobotMovesToGoal(robot_tester) # Add more tests here if desired @@ -237,11 +301,17 @@ def main(argv=sys.argv[1:]): # wait a few seconds to make sure entire stacks are up time.sleep(10) - # run tests on each robot for tester in testers: passed = run_all_tests(tester) if not passed: - exit(1) + break + + for tester in testers: + # stop and shutdown the nav stack to exit cleanly + tester.shutdown() + + if not passed: + exit(1) if __name__ == '__main__':