Nav2 Navigation Stack - humble  humble
ROS 2 Navigation Stack
drive_on_heading_behavior_tester.cpp
1 // Copyright (c) 2020 Sarthak Mittal
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License. Reserved.
15 
16 #include <string>
17 #include <random>
18 #include <tuple>
19 #include <memory>
20 #include <iostream>
21 #include <chrono>
22 #include <sstream>
23 #include <iomanip>
24 
25 #include "drive_on_heading_behavior_tester.hpp"
26 #include "nav2_util/geometry_utils.hpp"
27 
28 using namespace std::chrono_literals;
29 using namespace std::chrono; // NOLINT
30 
31 namespace nav2_system_tests
32 {
33 
34 DriveOnHeadingBehaviorTester::DriveOnHeadingBehaviorTester()
35 : is_active_(false),
36  initial_pose_received_(false)
37 {
38  rclcpp::NodeOptions options;
39  options.parameter_overrides({{"use_sim_time", true}});
40  node_ = rclcpp::Node::make_shared("DriveOnHeading_behavior_test", options);
41 
42  tf_buffer_ = std::make_shared<tf2_ros::Buffer>(node_->get_clock());
43  tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);
44 
45  client_ptr_ = rclcpp_action::create_client<DriveOnHeading>(
46  node_->get_node_base_interface(),
47  node_->get_node_graph_interface(),
48  node_->get_node_logging_interface(),
49  node_->get_node_waitables_interface(),
50  "drive_on_heading");
51 
52  publisher_ =
53  node_->create_publisher<geometry_msgs::msg::PoseWithCovarianceStamped>("initialpose", 10);
54 
55  subscription_ = node_->create_subscription<geometry_msgs::msg::PoseWithCovarianceStamped>(
56  "amcl_pose", rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable(),
57  std::bind(&DriveOnHeadingBehaviorTester::amclPoseCallback, this, std::placeholders::_1));
58 
59  stamp_ = node_->now();
60 }
61 
62 DriveOnHeadingBehaviorTester::~DriveOnHeadingBehaviorTester()
63 {
64  if (is_active_) {
65  deactivate();
66  }
67 }
68 
69 void DriveOnHeadingBehaviorTester::activate()
70 {
71  if (is_active_) {
72  throw std::runtime_error("Trying to activate while already active");
73  return;
74  }
75 
76  while (!initial_pose_received_) {
77  RCLCPP_WARN(node_->get_logger(), "Initial pose not received");
78  sendInitialPose();
79  std::this_thread::sleep_for(100ms);
80  rclcpp::spin_some(node_);
81  }
82 
83  // Wait for lifecycle_manager_navigation to activate behavior_server
84  std::this_thread::sleep_for(10s);
85 
86  if (!client_ptr_) {
87  RCLCPP_ERROR(node_->get_logger(), "Action client not initialized");
88  is_active_ = false;
89  return;
90  }
91 
92  if (!client_ptr_->wait_for_action_server(10s)) {
93  RCLCPP_ERROR(node_->get_logger(), "Action server not available after waiting");
94  is_active_ = false;
95  return;
96  }
97 
98  RCLCPP_INFO(this->node_->get_logger(), "DriveOnHeading action server is ready");
99  is_active_ = true;
100 }
101 
102 void DriveOnHeadingBehaviorTester::deactivate()
103 {
104  if (!is_active_) {
105  throw std::runtime_error("Trying to deactivate while already inactive");
106  }
107  is_active_ = false;
108 }
109 
110 bool DriveOnHeadingBehaviorTester::defaultDriveOnHeadingBehaviorTest(
111  const DriveOnHeading::Goal goal_msg,
112  const double tolerance)
113 {
114  if (!is_active_) {
115  RCLCPP_ERROR(node_->get_logger(), "Not activated");
116  return false;
117  }
118 
119  // Sleep to let behavior server be ready for serving in multiple runs
120  std::this_thread::sleep_for(5s);
121 
122  RCLCPP_INFO(this->node_->get_logger(), "Sending goal");
123 
124  geometry_msgs::msg::PoseStamped initial_pose;
125  if (!nav2_util::getCurrentPose(initial_pose, *tf_buffer_, "odom")) {
126  RCLCPP_ERROR(node_->get_logger(), "Current robot pose is not available.");
127  return false;
128  }
129  RCLCPP_INFO(node_->get_logger(), "Found current robot pose");
130 
131  auto goal_handle_future = client_ptr_->async_send_goal(goal_msg);
132 
133  if (rclcpp::spin_until_future_complete(node_, goal_handle_future) !=
134  rclcpp::FutureReturnCode::SUCCESS)
135  {
136  RCLCPP_ERROR(node_->get_logger(), "send goal call failed :(");
137  return false;
138  }
139 
140  rclcpp_action::ClientGoalHandle<DriveOnHeading>::SharedPtr goal_handle = goal_handle_future.get();
141  if (!goal_handle) {
142  RCLCPP_ERROR(node_->get_logger(), "Goal was rejected by server");
143  return false;
144  }
145 
146  // Wait for the server to be done with the goal
147  auto result_future = client_ptr_->async_get_result(goal_handle);
148 
149  RCLCPP_INFO(node_->get_logger(), "Waiting for result");
150  if (rclcpp::spin_until_future_complete(node_, result_future) !=
151  rclcpp::FutureReturnCode::SUCCESS)
152  {
153  RCLCPP_ERROR(node_->get_logger(), "get result call failed :(");
154  return false;
155  }
156 
157  rclcpp_action::ClientGoalHandle<DriveOnHeading>::WrappedResult wrapped_result =
158  result_future.get();
159 
160  switch (wrapped_result.code) {
161  case rclcpp_action::ResultCode::SUCCEEDED: break;
162  case rclcpp_action::ResultCode::ABORTED: RCLCPP_ERROR(
163  node_->get_logger(),
164  "Goal was aborted");
165  return false;
166  case rclcpp_action::ResultCode::CANCELED: RCLCPP_ERROR(
167  node_->get_logger(),
168  "Goal was canceled");
169  return false;
170  default: RCLCPP_ERROR(node_->get_logger(), "Unknown result code");
171  return false;
172  }
173 
174  RCLCPP_INFO(node_->get_logger(), "result received");
175 
176  geometry_msgs::msg::PoseStamped current_pose;
177  if (!nav2_util::getCurrentPose(current_pose, *tf_buffer_, "odom")) {
178  RCLCPP_ERROR(node_->get_logger(), "Current robot pose is not available.");
179  return false;
180  }
181 
182  double dist = nav2_util::geometry_utils::euclidean_distance(initial_pose, current_pose);
183 
184  if (fabs(dist) > fabs(goal_msg.target.x) + tolerance) {
185  RCLCPP_ERROR(
186  node_->get_logger(),
187  "Distance from goal is %lf (tolerance %lf)",
188  fabs(dist - goal_msg.target.x), tolerance);
189  return false;
190  }
191 
192  return true;
193 }
194 
195 void DriveOnHeadingBehaviorTester::sendInitialPose()
196 {
197  geometry_msgs::msg::PoseWithCovarianceStamped pose;
198  pose.header.frame_id = "map";
199  pose.header.stamp = stamp_;
200  pose.pose.pose.position.x = -2.0;
201  pose.pose.pose.position.y = -0.5;
202  pose.pose.pose.position.z = 0.0;
203  pose.pose.pose.orientation.x = 0.0;
204  pose.pose.pose.orientation.y = 0.0;
205  pose.pose.pose.orientation.z = 0.0;
206  pose.pose.pose.orientation.w = 1.0;
207  for (int i = 0; i < 35; i++) {
208  pose.pose.covariance[i] = 0.0;
209  }
210  pose.pose.covariance[0] = 0.08;
211  pose.pose.covariance[7] = 0.08;
212  pose.pose.covariance[35] = 0.05;
213 
214  publisher_->publish(pose);
215  RCLCPP_INFO(node_->get_logger(), "Sent initial pose");
216 }
217 
218 void DriveOnHeadingBehaviorTester::amclPoseCallback(
219  const geometry_msgs::msg::PoseWithCovarianceStamped::SharedPtr)
220 {
221  initial_pose_received_ = true;
222 }
223 
224 } // namespace nav2_system_tests