graceful_controller_tests.cpp
Go to the documentation of this file.
1 /*********************************************************************
2 *
3 * Software License Agreement (BSD License)
4 *
5 * Copyright (c) 2021, Michael Ferguson
6 * Copyright (c) 2009, Willow Garage, Inc.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * * Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials provided
18 * with the distribution.
19 * * Neither the name of Willow Garage, Inc. nor the names of its
20 * contributors may be used to endorse or promote products derived
21 * from this software without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 *
36 * Author: Michael Ferguson
37 *********************************************************************/
38 
39 #include <gtest/gtest.h>
40 #include <ros/ros.h>
42 #include <nav_msgs/OccupancyGrid.h>
43 #include <nav_msgs/Odometry.h>
45 #include <std_msgs/Float32.h>
48 #include <tf2/utils.h>
49 
50 // Only needed for computeDistanceAlongPath tests
52 
54 {
55 public:
57  loader_("nav_core", "nav_core::BaseLocalPlanner"),
59  costmap_ros_(NULL),
60  shutdown_(false)
61  {
62  }
63 
64  bool setup(bool intialize = true)
65  {
66  ros::NodeHandle nh("~");
67 
68  // ROS topics to run the test
69  map_pub_ = nh.advertise<nav_msgs::OccupancyGrid>("/map", 1, true /* latch */);
70  odom_pub_ = nh.advertise<nav_msgs::Odometry>("/odom", 1);
71  max_vel_pub_ = nh.advertise<std_msgs::Float32>("/max_vel_x", 1, true /* latch */);
72 
73  // Need to start publishing odom before we initialize the costmap
74  resetMap();
75  resetPose();
76  thread_ = new boost::thread(boost::bind(&ControllerFixture::updateThread, this));
77 
78  // Costmap for testing
80 
81  // Controller instance
82  try
83  {
84  controller_ = loader_.createInstance("graceful_controller/GracefulControllerROS");
85  }
86  catch (const pluginlib::PluginlibException& ex)
87  {
88  ROS_FATAL("Failed to create the controller. Exception: %s", ex.what());
89  return false;
90  }
91 
92  if (intialize)
93  {
94  initialize();
95  }
96 
97  return true;
98  }
99 
101  {
102  // Stop costmap updates - otherwise this can fail with a boost::lock issue
103  costmap_ros_->stop();
104  // Stop our own update thread
105  shutdown_ = true;
106  thread_->join();
107  delete thread_;
108  }
109 
110  void initialize()
111  {
112  controller_->initialize(loader_.getName("graceful_controller/GracefulControllerROS"),
114  }
115 
117  {
118  return controller_;
119  }
120 
122  {
123  return costmap_ros_;
124  }
125 
126  void resetMap()
127  {
128  map_.header.frame_id = "map";
129  map_.info.resolution = 0.05;
130  map_.info.width = 100;
131  map_.info.height = 100;
132  map_.info.origin.position.x = -2.5;
133  map_.info.origin.position.y = -2.5;
134 
135  map_.data.resize(100 * 100);
136  for (size_t i = 0; i < map_.data.size(); ++i)
137  {
138  map_.data[i] = 0;
139  }
140  }
141 
142  bool markMap(double x, double y)
143  {
144  int ix = (x - map_.info.origin.position.x) / map_.info.resolution;
145  int iy = (y - map_.info.origin.position.y) / map_.info.resolution;
146 
147  if (ix < 0 || ix >= static_cast<int>(map_.info.width) ||
148  iy < 0 || iy >= static_cast<int>(map_.info.height))
149  {
150  return false;
151  }
152 
153  map_.data[ix + (iy * map_.info.width)] = 100;
155  return true;
156  }
157 
158  void resetPose()
159  {
160  odom_.header.frame_id = "odom";
161  odom_.child_frame_id = "base_link";
162  odom_.pose.pose.position.x = 0.0;
163  odom_.pose.pose.position.y = 0.0;
164  odom_.pose.pose.orientation.w = 1.0;
165  }
166 
167  void setPose(double x, double y, double yaw)
168  {
169  odom_.header.frame_id = "odom";
170  odom_.child_frame_id = "base_link";
171  odom_.pose.pose.position.x = x;
172  odom_.pose.pose.position.y = y;
173  odom_.pose.pose.orientation.z = sin(yaw / 2.0);
174  odom_.pose.pose.orientation.w = cos(yaw / 2.0);
175  }
176 
177  void setMaxVelocity(float velocity)
178  {
179  std_msgs::Float32 msg;
180  msg.data = velocity;
181  max_vel_pub_.publish(msg);
182  }
183 
184  void setSimVelocity(double x, double th)
185  {
186  odom_.twist.twist.linear.x = x;
187  odom_.twist.twist.angular.z = th;
188  }
189 
190  void setSimCommand(geometry_msgs::Twist& command)
191  {
192  command_ = command;
193  }
194 
195 protected:
196  void updateThread()
197  {
199 
200  ros::Rate r(20.0);
201  while (ros::ok() && !shutdown_)
202  {
203  // Propagate position
204  double yaw = tf2::getYaw(odom_.pose.pose.orientation);
205  odom_.pose.pose.position.x += 0.05 * command_.linear.x * cos(yaw);
206  odom_.pose.pose.position.y += 0.05 * command_.linear.x * sin(yaw);
207  yaw += 0.05 * command_.angular.z;
208  odom_.pose.pose.orientation.z = sin(yaw / 2.0);
209  odom_.pose.pose.orientation.w = cos(yaw / 2.0);
210 
211  // Publish updated odom
212  odom_.header.stamp = ros::Time::now();
214 
215  // Fake localization
216  geometry_msgs::TransformStamped transform;
217  transform.header.stamp = ros::Time::now();
218  transform.header.frame_id = "map";
219  transform.child_frame_id = odom_.header.frame_id;
220  transform.transform.rotation.w = 1.0;
221  broadcaster_.sendTransform(transform);
222 
223  // Publish updated odom as TF
224  transform.header.frame_id = odom_.header.frame_id;
225  transform.child_frame_id = odom_.child_frame_id;
226  transform.transform.translation.x = odom_.pose.pose.position.x;
227  transform.transform.translation.y = odom_.pose.pose.position.y;
228  transform.transform.rotation = odom_.pose.pose.orientation;
229  broadcaster_.sendTransform(transform);
230 
231  ros::spinOnce();
232  r.sleep();
233  }
234  }
235 
243  nav_msgs::OccupancyGrid map_;
244  nav_msgs::Odometry odom_;
245  geometry_msgs::Twist command_;
246  boost::thread* thread_;
247  bool shutdown_;
248 };
249 
250 TEST(ControllerTests, test_basic_plan)
251 {
252  ControllerFixture fixture;
253  ASSERT_TRUE(fixture.setup(false /* do not intialize */));
255 
256  std::vector<geometry_msgs::PoseStamped> plan;
257  geometry_msgs::PoseStamped pose;
258  pose.header.frame_id = "map";
259  pose.pose.orientation.w = 1.0;
260  pose.pose.position.x = 1.0;
261  pose.pose.position.y = 0.0;
262  plan.push_back(pose);
263  pose.pose.position.x = 1.5;
264  pose.pose.position.y = 0.0;
265  plan.push_back(pose);
266 
267  // Unitialized controller should not be able to plan
268  EXPECT_FALSE(controller->setPlan(plan));
269  geometry_msgs::Twist command;
270  EXPECT_FALSE(controller->computeVelocityCommands(command));
271  EXPECT_FALSE(controller->isGoalReached());
272  fixture.initialize();
273 
274  // Calling multiple times should not be a problem
275  fixture.initialize();
276 
277  // Now we can set the plan
278  EXPECT_TRUE(controller->setPlan(plan));
279 
280  // Set velocity to 0
281  fixture.setSimVelocity(0.0, 0.0);
283 
284  // Odom reports velocity = 0, but min_vel_x is greater than acc_lim * acc_dt
285  EXPECT_TRUE(controller->computeVelocityCommands(command));
286  EXPECT_EQ(command.linear.x, 0.25);
287  EXPECT_EQ(command.angular.z, 0.0);
288  EXPECT_FALSE(controller->isGoalReached());
289 
290  // Set a new max velocity by topic
291  fixture.setMaxVelocity(0.5);
292  ros::Duration(0.25).sleep();
293 
294  // Odom still reports 0, so max remains the same
295  EXPECT_TRUE(controller->computeVelocityCommands(command));
296  EXPECT_EQ(command.linear.x, 0.25);
297  EXPECT_EQ(command.angular.z, 0.0);
298 
299  // Set current velocity above the velocity limit
300  fixture.setSimVelocity(1.0, 0.0);
301  ros::Duration(0.25).sleep();
302 
303  // Odom now reports 1.0, but max_vel_x topic is 0.5 - will deccelerate down to 0.5
304  EXPECT_TRUE(controller->computeVelocityCommands(command));
305  EXPECT_EQ(command.linear.x, 0.875);
306  EXPECT_EQ(command.angular.z, 0.0);
307 
308  // Bump up our limit and speed
309  fixture.setSimVelocity(1.0, 0.0);
310  fixture.setMaxVelocity(1.0);
311  ros::Duration(0.25).sleep();
312 
313  // Expect max velocity
314  EXPECT_TRUE(controller->computeVelocityCommands(command));
315  EXPECT_EQ(command.linear.x, 1.0);
316  EXPECT_EQ(command.angular.z, 0.0);
317 
318  // Report velocity over limits
319  fixture.setSimVelocity(3.0, 0.0);
320  ros::Duration(0.25).sleep();
321 
322  // Expect max velocity
323  EXPECT_TRUE(controller->computeVelocityCommands(command));
324  EXPECT_EQ(command.linear.x, 1.0);
325  EXPECT_EQ(command.angular.z, 0.0);
326 }
327 
328 TEST(ControllerTests, test_out_of_range)
329 {
330  ControllerFixture fixture;
331  ASSERT_TRUE(fixture.setup());
333 
334  std::vector<geometry_msgs::PoseStamped> plan;
335  geometry_msgs::PoseStamped pose;
336  pose.header.frame_id = "map";
337  pose.pose.orientation.w = 1.0;
338  pose.pose.position.x = 1.5;
339  pose.pose.position.y = 0.0;
340  plan.push_back(pose);
341  EXPECT_TRUE(controller->setPlan(plan));
342 
343  // First pose is beyond the lookahead - should fail
344  geometry_msgs::Twist command;
345  EXPECT_FALSE(controller->computeVelocityCommands(command));
346 }
347 
348 TEST(ControllerTests, test_initial_rotate_in_place)
349 {
350  ControllerFixture fixture;
351  ASSERT_TRUE(fixture.setup());
353 
354  std::vector<geometry_msgs::PoseStamped> plan;
355  geometry_msgs::PoseStamped pose;
356  pose.header.frame_id = "map";
357  pose.pose.orientation.w = 1.0;
358  pose.pose.position.x = -0.5;
359  pose.pose.position.y = 0.0;
360  plan.push_back(pose);
361  pose.pose.position.x = -1.0;
362  pose.pose.position.y = 0.0;
363  plan.push_back(pose);
364  EXPECT_TRUE(controller->setPlan(plan));
365 
366  // Set our velocity to 0
367  fixture.setSimVelocity(0.0, 0.0);
368  ros::Duration(0.25).sleep();
369 
370  // Odom reports velocity = 0, but min_in_place_vel_theta is 0.6
371  geometry_msgs::Twist command;
372  EXPECT_TRUE(controller->computeVelocityCommands(command));
373  EXPECT_EQ(command.linear.x, 0.0);
374  EXPECT_EQ(command.angular.z, 0.6);
375 
376  // Set our velocity to 1.0
377  fixture.setSimVelocity(0.0, 1.0);
378  ros::Duration(0.25).sleep();
379 
380  // Expect limited rotation command
381  EXPECT_TRUE(controller->computeVelocityCommands(command));
382  EXPECT_EQ(command.linear.x, 0.0);
383  EXPECT_EQ(command.angular.z, 1.25);
384 
385  // Report velocity over limits
386  fixture.setSimVelocity(0.0, 4.0);
387  ros::Duration(0.25).sleep();
388 
389  // Expect max rotation
390  EXPECT_TRUE(controller->computeVelocityCommands(command));
391  EXPECT_EQ(command.linear.x, 0.0);
392  EXPECT_EQ(command.angular.z, 2.5);
393 }
394 
395 TEST(ControllerTests, test_final_rotate_in_place)
396 {
397  ControllerFixture fixture;
398  ASSERT_TRUE(fixture.setup());
400 
401  std::vector<geometry_msgs::PoseStamped> plan;
402  geometry_msgs::PoseStamped pose;
403  pose.header.frame_id = "map";
404  pose.pose.orientation.w = 1.0;
405  pose.pose.position.x = 0.5;
406  pose.pose.position.y = 0.0;
407  plan.push_back(pose);
408  EXPECT_TRUE(controller->setPlan(plan));
409 
410  // Set pose to start
411  fixture.setPose(0.1, 0.0, 0.0);
412  ros::Duration(0.25).sleep();
413 
414  // Expect forward motion at min velocity since we are aligned with only goal pose
415  geometry_msgs::Twist command;
416  EXPECT_TRUE(controller->computeVelocityCommands(command));
417  EXPECT_EQ(command.linear.x, 0.25);
418  EXPECT_EQ(command.angular.z, 0.0);
419 
420  // Set our pose to the end goal, but with bad heading
421  fixture.setPose(0.5, 0.0, 1.57);
422  ros::Duration(0.25).sleep();
423 
424  // Expect limited rotation command
425  EXPECT_TRUE(controller->computeVelocityCommands(command));
426  EXPECT_EQ(command.linear.x, 0.0);
427  EXPECT_EQ(command.angular.z, -0.6);
428  EXPECT_FALSE(controller->isGoalReached());
429 }
430 
431 TEST(ControllerTests, test_collision_check)
432 {
433  ControllerFixture fixture;
434  ASSERT_TRUE(fixture.setup());
436 
437  fixture.markMap(0.65, 0);
438  ros::Duration(0.25).sleep();
439 
440  std::vector<geometry_msgs::PoseStamped> plan;
441  geometry_msgs::PoseStamped pose;
442  pose.header.frame_id = "map";
443  pose.pose.orientation.w = 1.0;
444  pose.pose.position.x = 0.5;
445  pose.pose.position.y = 0.0;
446  plan.push_back(pose);
447  EXPECT_TRUE(controller->setPlan(plan));
448 
449  // Expect no command
450  geometry_msgs::Twist command;
451  EXPECT_FALSE(controller->computeVelocityCommands(command));
452 }
453 
454 TEST(ControllerTests, test_compute_distance_along_path)
455 {
456  std::vector<geometry_msgs::PoseStamped> poses;
457  std::vector<double> distances;
458 
459  // Simple set of poses
460  for (int i = 0; i < 5; ++i)
461  {
462  geometry_msgs::PoseStamped pose;
463  pose.pose.position.x = (i - 2);
464  pose.pose.position.y = 0;
465  poses.push_back(pose);
466  }
467 
468  // Check distances
470  EXPECT_EQ(distances[0], 2);
471  EXPECT_EQ(distances[1], 1);
472  EXPECT_EQ(distances[2], 0);
473  EXPECT_EQ(distances[3], 1);
474  EXPECT_EQ(distances[4], 2);
475 
476  // Make path wrap around
477  distances.clear();
478  {
479  geometry_msgs::PoseStamped pose;
480  pose.pose.position.x = 2;
481  pose.pose.position.y = 1;
482  poses.push_back(pose);
483  pose.pose.position.x = 1;
484  pose.pose.position.y = 1;
485  poses.push_back(pose);
486  pose.pose.position.x = 0;
487  pose.pose.position.y = 1;
488  poses.push_back(pose);
489  }
490 
491  // Check distances
493  EXPECT_EQ(distances[0], 2);
494  EXPECT_EQ(distances[1], 1);
495  EXPECT_EQ(distances[2], 0);
496  EXPECT_EQ(distances[3], 1);
497  EXPECT_EQ(distances[4], 2);
498  EXPECT_EQ(distances[5], 3);
499  EXPECT_EQ(distances[6], 4);
500  EXPECT_EQ(distances[7], 5);
501 }
502 
503 int main(int argc, char** argv)
504 {
505  ros::init(argc, argv, "graceful_controller_tests");
506  testing::InitGoogleTest(&argc, argv);
507  return RUN_ALL_TESTS();
508 }
TEST
TEST(ControllerTests, test_basic_plan)
Definition: graceful_controller_tests.cpp:250
ros::Publisher
boost::shared_ptr< nav_core::BaseLocalPlanner >
graceful_controller_ros.hpp
ros::init
ROSCPP_DECL void init(const M_string &remappings, const std::string &name, uint32_t options=0)
ControllerFixture::setup
bool setup(bool intialize=true)
Definition: graceful_controller_tests.cpp:100
ControllerFixture::ControllerFixture
ControllerFixture()
Definition: graceful_controller_tests.cpp:92
tf2::getYaw
double getYaw(const A &a)
utils.h
ros.h
ControllerFixture::setPose
void setPose(double x, double y, double yaw)
Definition: graceful_controller_tests.cpp:203
command
ROSLIB_DECL std::string command(const std::string &cmd)
ros::spinOnce
ROSCPP_DECL void spinOnce()
ControllerFixture::map_pub_
ros::Publisher map_pub_
Definition: graceful_controller_tests.cpp:278
ControllerFixture::~ControllerFixture
~ControllerFixture()
Definition: graceful_controller_tests.cpp:136
ControllerFixture::thread_
boost::thread * thread_
Definition: graceful_controller_tests.cpp:282
ControllerFixture::setMaxVelocity
void setMaxVelocity(float velocity)
Definition: graceful_controller_tests.cpp:213
ControllerFixture::max_vel_pub_
ros::Publisher max_vel_pub_
Definition: graceful_controller_tests.cpp:278
ros::Publisher::publish
void publish(const boost::shared_ptr< M > &message) const
ros::NodeHandle::advertise
Publisher advertise(AdvertiseOptions &ops)
ros::ok
ROSCPP_DECL bool ok()
transform_broadcaster.h
ControllerFixture::shutdown_
bool shutdown_
Definition: graceful_controller_tests.cpp:283
pluginlib::PluginlibException
tf2_ros::TransformListener
ControllerFixture::getController
boost::shared_ptr< nav_core::BaseLocalPlanner > getController()
Definition: graceful_controller_tests.cpp:152
base_local_planner.h
ControllerFixture::map_
nav_msgs::OccupancyGrid map_
Definition: graceful_controller_tests.cpp:279
tf2_ros::TransformBroadcaster::sendTransform
void sendTransform(const geometry_msgs::TransformStamped &transform)
ControllerFixture::resetPose
void resetPose()
Definition: graceful_controller_tests.cpp:194
ControllerFixture::listener_
tf2_ros::TransformListener listener_
Definition: graceful_controller_tests.cpp:275
graceful_controller::computeDistanceAlongPath
void computeDistanceAlongPath(const std::vector< geometry_msgs::PoseStamped > &poses, std::vector< double > &distances)
Compute distance of poses along a path. Assumes poses are in robot-centric frame.
Definition: graceful_controller_ros.cpp:834
ControllerFixture::buffer_
tf2_ros::Buffer buffer_
Definition: graceful_controller_tests.cpp:274
ControllerFixture::controller_
boost::shared_ptr< nav_core::BaseLocalPlanner > controller_
Definition: graceful_controller_tests.cpp:273
tf2_ros::Buffer
pluginlib::ClassLoader< nav_core::BaseLocalPlanner >
class_loader.hpp
ros::Rate::sleep
bool sleep()
ROS_FATAL
#define ROS_FATAL(...)
ControllerFixture::odom_
nav_msgs::Odometry odom_
Definition: graceful_controller_tests.cpp:280
transform_listener.h
pluginlib::ClassLoader::createInstance
boost::shared_ptr< T > createInstance(const std::string &lookup_name)
pluginlib::ClassLoader::getName
virtual std::string getName(const std::string &lookup_name)
ControllerFixture
Definition: graceful_controller_tests.cpp:53
costmap_2d::Costmap2DROS::stop
void stop()
ControllerFixture::resetMap
void resetMap()
Definition: graceful_controller_tests.cpp:162
ControllerFixture::updateThread
void updateThread()
Definition: graceful_controller_tests.cpp:232
ControllerFixture::odom_pub_
ros::Publisher odom_pub_
Definition: graceful_controller_tests.cpp:278
tf2_ros::TransformBroadcaster
ros::Rate
ControllerFixture::markMap
bool markMap(double x, double y)
Definition: graceful_controller_tests.cpp:178
ros::Duration::sleep
bool sleep() const
ControllerFixture::getCostmap
costmap_2d::Costmap2DROS * getCostmap()
Definition: graceful_controller_tests.cpp:157
ControllerFixture::costmap_ros_
costmap_2d::Costmap2DROS * costmap_ros_
Definition: graceful_controller_tests.cpp:277
ControllerFixture::command_
geometry_msgs::Twist command_
Definition: graceful_controller_tests.cpp:281
ControllerFixture::setSimVelocity
void setSimVelocity(double x, double th)
Definition: graceful_controller_tests.cpp:220
ros::Duration
ControllerFixture::loader_
pluginlib::ClassLoader< nav_core::BaseLocalPlanner > loader_
Definition: graceful_controller_tests.cpp:272
costmap_2d::Costmap2DROS
ControllerFixture::initialize
void initialize()
Definition: graceful_controller_tests.cpp:146
main
int main(int argc, char **argv)
Definition: graceful_controller_tests.cpp:503
ControllerFixture::broadcaster_
tf2_ros::TransformBroadcaster broadcaster_
Definition: graceful_controller_tests.cpp:276
ros::NodeHandle
ros::Time::now
static Time now()
ControllerFixture::setSimCommand
void setSimCommand(geometry_msgs::Twist &command)
Definition: graceful_controller_tests.cpp:226


graceful_controller_ros
Author(s): Michael Ferguson
autogenerated on Wed Oct 23 2024 02:43:04