Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
assisted_teleop_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 "assisted_teleop_behavior_tester.hpp"
26 #include "nav2_util/geometry_utils.hpp"
27 #include "nav2_ros_common/tf2_factories.hpp"
28 
29 using namespace std::chrono_literals;
30 using namespace std::chrono; // NOLINT
31 
32 namespace nav2_system_tests
33 {
34 
35 AssistedTeleopBehaviorTester::AssistedTeleopBehaviorTester()
36 : is_active_(false),
37  initial_pose_received_(false)
38 {
39  node_ = rclcpp::Node::make_shared(
40  "assisted_teleop_behavior_test",
41  rclcpp::NodeOptions().parameter_overrides(
42  {rclcpp::Parameter("use_sim_time", true)}));
43 
44  tf_buffer_ = nav2::create_transform_buffer(node_);
45  tf_listener_ = nav2::create_transform_listener(*tf_buffer_);
46 
47  client_ptr_ = rclcpp_action::create_client<AssistedTeleop>(
48  node_->get_node_base_interface(),
49  node_->get_node_graph_interface(),
50  node_->get_node_logging_interface(),
51  node_->get_node_waitables_interface(),
52  "assisted_teleop");
53 
54  initial_pose_pub_ =
55  node_->create_publisher<geometry_msgs::msg::PoseWithCovarianceStamped>("initialpose", 10);
56 
57  preempt_pub_ =
58  node_->create_publisher<std_msgs::msg::Empty>("preempt_teleop", 10);
59 
60  cmd_vel_pub_ =
61  node_->create_publisher<geometry_msgs::msg::TwistStamped>("cmd_vel_teleop", 10);
62 
63  subscription_ = node_->create_subscription<geometry_msgs::msg::PoseWithCovarianceStamped>(
64  "amcl_pose", rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable(),
65  std::bind(&AssistedTeleopBehaviorTester::amclPoseCallback, this, std::placeholders::_1));
66 
67  filtered_vel_sub_ = node_->create_subscription<geometry_msgs::msg::TwistStamped>(
68  "cmd_vel",
69  rclcpp::SystemDefaultsQoS(),
70  std::bind(&AssistedTeleopBehaviorTester::filteredVelCallback, this, std::placeholders::_1));
71 
72  std::string costmap_topic = "/local_costmap/costmap_raw";
73  std::string footprint_topic = "/local_costmap/published_footprint";
74 
75  costmap_sub_ = std::make_shared<nav2_costmap_2d::CostmapSubscriber>(
76  node_,
77  costmap_topic);
78 
79  footprint_sub_ = std::make_shared<nav2_costmap_2d::FootprintSubscriber>(
80  node_,
81  footprint_topic,
82  *tf_buffer_);
83 
84  collision_checker_ = std::make_unique<nav2_costmap_2d::CostmapTopicCollisionChecker>(
85  *costmap_sub_,
86  *footprint_sub_
87  );
88 
89  stamp_ = node_->now();
90  executor_.add_node(node_);
91 }
92 
93 AssistedTeleopBehaviorTester::~AssistedTeleopBehaviorTester()
94 {
95  if (is_active_) {
96  deactivate();
97  }
98 }
99 
100 void AssistedTeleopBehaviorTester::activate()
101 {
102  if (is_active_) {
103  throw std::runtime_error("Trying to activate while already active");
104  return;
105  }
106 
107  while (!initial_pose_received_) {
108  RCLCPP_WARN(node_->get_logger(), "Initial pose not received");
109  sendInitialPose();
110  std::this_thread::sleep_for(100ms);
111  executor_.spin_some();
112  }
113 
114  // Wait for lifecycle_manager_navigation to activate behavior_server
115  std::this_thread::sleep_for(10s);
116 
117  if (!client_ptr_) {
118  RCLCPP_ERROR(node_->get_logger(), "Action client not initialized");
119  is_active_ = false;
120  return;
121  }
122 
123  if (!client_ptr_->wait_for_action_server(10s)) {
124  RCLCPP_ERROR(node_->get_logger(), "Action server not available after waiting");
125  is_active_ = false;
126  return;
127  }
128 
129  RCLCPP_INFO(this->node_->get_logger(), "Assisted Teleop action server is ready");
130  is_active_ = true;
131 }
132 
133 void AssistedTeleopBehaviorTester::deactivate()
134 {
135  if (!is_active_) {
136  throw std::runtime_error("Trying to deactivate while already inactive");
137  }
138  is_active_ = false;
139 }
140 
141 bool AssistedTeleopBehaviorTester::defaultAssistedTeleopTest(
142  const float lin_vel,
143  const float ang_vel)
144 {
145  if (!is_active_) {
146  RCLCPP_ERROR(node_->get_logger(), "Not activated");
147  return false;
148  }
149 
150  RCLCPP_INFO(node_->get_logger(), "Sending goal");
151 
152  auto goal_handle_future = client_ptr_->async_send_goal(nav2_msgs::action::AssistedTeleop::Goal());
153 
154  if (executor_.spin_until_future_complete(goal_handle_future) !=
155  rclcpp::FutureReturnCode::SUCCESS)
156  {
157  RCLCPP_ERROR(node_->get_logger(), "send goal call failed :(");
158  return false;
159  }
160 
161  rclcpp_action::ClientGoalHandle<AssistedTeleop>::SharedPtr goal_handle = goal_handle_future.get();
162  if (!goal_handle) {
163  RCLCPP_ERROR(node_->get_logger(), "Goal was rejected by server");
164  return false;
165  }
166 
167  // Wait for the server to be done with the goal
168  auto result_future = client_ptr_->async_get_result(goal_handle);
169 
170  // Stream commands faster than the behavior's teleop_command_timeout (0.25s)
171  rclcpp::Rate r(20);
172 
173  counter_ = 0;
174  auto start_time = std::chrono::system_clock::now();
175  auto stopped_time = std::chrono::system_clock::now();
176  while (rclcpp::ok()) {
177  geometry_msgs::msg::TwistStamped cmd_vel = geometry_msgs::msg::TwistStamped();
178  cmd_vel.header.stamp = node_->now();
179  cmd_vel.twist.linear.x = lin_vel;
180  cmd_vel.twist.angular.z = ang_vel;
181  cmd_vel_pub_->publish(cmd_vel);
182 
183  // Only preempt once the behavior has held the robot stopped for a while. A single
184  // zero command is transient while it creeps up to the obstacle and the pose is
185  // then still marginal for the collision check below.
186  if (counter_ == 0) {
187  stopped_time = std::chrono::system_clock::now();
188  } else if (std::chrono::system_clock::now() - stopped_time > 1s) {
189  break;
190  }
191 
192  auto current_time = std::chrono::system_clock::now();
193  if (current_time - start_time > 25s) {
194  RCLCPP_ERROR(node_->get_logger(), "Exceeded Timeout");
195  return false;
196  }
197 
198  executor_.spin_some();
199  r.sleep();
200  }
201 
202  auto preempt_msg = std_msgs::msg::Empty();
203  preempt_pub_->publish(preempt_msg);
204 
205  RCLCPP_INFO(node_->get_logger(), "Waiting for result");
206  if (executor_.spin_until_future_complete(result_future) !=
207  rclcpp::FutureReturnCode::SUCCESS)
208  {
209  RCLCPP_ERROR(node_->get_logger(), "get result call failed :(");
210  return false;
211  }
212 
213  rclcpp_action::ClientGoalHandle<AssistedTeleop>::WrappedResult
214  wrapped_result = result_future.get();
215 
216  switch (wrapped_result.code) {
217  case rclcpp_action::ResultCode::SUCCEEDED: break;
218  case rclcpp_action::ResultCode::ABORTED: RCLCPP_ERROR(
219  node_->get_logger(),
220  "Goal was aborted");
221  return false;
222  case rclcpp_action::ResultCode::CANCELED: RCLCPP_ERROR(
223  node_->get_logger(),
224  "Goal was canceled");
225  return false;
226  default: RCLCPP_ERROR(node_->get_logger(), "Unknown result code");
227  return false;
228  }
229 
230  RCLCPP_INFO(node_->get_logger(), "result received");
231 
232  geometry_msgs::msg::PoseStamped current_pose;
233  if (!nav2_util::getCurrentPose(current_pose, *tf_buffer_, "odom")) {
234  RCLCPP_ERROR(node_->get_logger(), "Current robot pose is not available.");
235  return false;
236  }
237 
238  if (!collision_checker_->isCollisionFree(current_pose.pose)) {
239  RCLCPP_ERROR(node_->get_logger(), "Ended in collision");
240  return false;
241  }
242 
243  return true;
244 }
245 
246 void AssistedTeleopBehaviorTester::sendInitialPose()
247 {
248  geometry_msgs::msg::PoseWithCovarianceStamped pose;
249  pose.header.frame_id = "map";
250  pose.header.stamp = stamp_;
251  pose.pose.pose.position.x = -2.0;
252  pose.pose.pose.position.y = -0.5;
253  pose.pose.pose.position.z = 0.0;
254  pose.pose.pose.orientation.x = 0.0;
255  pose.pose.pose.orientation.y = 0.0;
256  pose.pose.pose.orientation.z = 0.0;
257  pose.pose.pose.orientation.w = 1.0;
258  for (int i = 0; i < 35; i++) {
259  pose.pose.covariance[i] = 0.0;
260  }
261  pose.pose.covariance[0] = 0.08;
262  pose.pose.covariance[7] = 0.08;
263  pose.pose.covariance[35] = 0.05;
264 
265  initial_pose_pub_->publish(pose);
266  RCLCPP_INFO(node_->get_logger(), "Sent initial pose");
267 }
268 
269 void AssistedTeleopBehaviorTester::amclPoseCallback(
270  const geometry_msgs::msg::PoseWithCovarianceStamped::ConstSharedPtr)
271 {
272  initial_pose_received_ = true;
273 }
274 
275 void AssistedTeleopBehaviorTester::filteredVelCallback(
276  geometry_msgs::msg::TwistStamped::ConstSharedPtr msg)
277 {
278  if (msg->twist.linear.x == 0.0f) {
279  counter_++;
280  } else {
281  counter_ = 0;
282  }
283 }
284 
285 } // namespace nav2_system_tests