00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030 #include <string>
00031 #include <vector>
00032
00033 #include <boost/thread.hpp>
00034
00035 #include <Eigen/Core>
00036 #include <Eigen/Geometry>
00037
00038 #include <ros/ros.h>
00039
00040 #include <geometry_msgs/Twist.h>
00041 #include <nav_msgs/Path.h>
00042 #include <rosgraph_msgs/Clock.h>
00043 #include <tf2_geometry_msgs/tf2_geometry_msgs.h>
00044 #include <tf2_ros/transform_broadcaster.h>
00045
00046 #include <trajectory_tracker_msgs/TrajectoryTrackerStatus.h>
00047 #include <trajectory_tracker_msgs/PathWithVelocity.h>
00048
00049 #include <gtest/gtest.h>
00050
00051 class TrajectoryTrackerTest : public ::testing::Test
00052 {
00053 private:
00054 ros::NodeHandle nh_;
00055 ros::Subscriber sub_cmd_vel_;
00056 ros::Subscriber sub_status_;
00057 ros::Publisher pub_path_;
00058 ros::Publisher pub_path_vel_;
00059 tf2_ros::TransformBroadcaster tfb_;
00060
00061 ros::Time cmd_vel_time_;
00062
00063 void cbStatus(const trajectory_tracker_msgs::TrajectoryTrackerStatus::ConstPtr& msg)
00064 {
00065 status_ = msg;
00066 }
00067 void cbCmdVel(const geometry_msgs::Twist::ConstPtr& msg)
00068 {
00069 const ros::Time now = ros::Time::now();
00070 if (cmd_vel_time_ == ros::Time(0))
00071 cmd_vel_time_ = now;
00072 const float dt = (now - cmd_vel_time_).toSec();
00073
00074 yaw_ += msg->angular.z * dt;
00075 pos_ += Eigen::Vector2d(std::cos(yaw_), std::sin(yaw_)) * msg->linear.x * dt;
00076 cmd_vel_time_ = now;
00077 cmd_vel_ = msg;
00078 }
00079
00080 public:
00081 Eigen::Vector2d pos_;
00082 double yaw_;
00083 trajectory_tracker_msgs::TrajectoryTrackerStatus::ConstPtr status_;
00084 geometry_msgs::Twist::ConstPtr cmd_vel_;
00085
00086 TrajectoryTrackerTest()
00087 : nh_("")
00088 {
00089 sub_cmd_vel_ = nh_.subscribe(
00090 "cmd_vel", 1, &TrajectoryTrackerTest::cbCmdVel, this);
00091 sub_status_ = nh_.subscribe(
00092 "trajectory_tracker/status", 1, &TrajectoryTrackerTest::cbStatus, this);
00093 pub_path_ = nh_.advertise<nav_msgs::Path>("path", 1, true);
00094 pub_path_vel_ = nh_.advertise<trajectory_tracker_msgs::PathWithVelocity>("path_velocity", 1, true);
00095 }
00096 void initState(const Eigen::Vector2d& pos, const float yaw)
00097 {
00098 nav_msgs::Path path;
00099 path.header.frame_id = "odom";
00100 path.header.stamp = ros::Time::now();
00101 pub_path_.publish(path);
00102
00103
00104 ros::Rate rate(10);
00105 while (ros::ok())
00106 {
00107 yaw_ = yaw;
00108 pos_ = pos;
00109 publishTransform();
00110
00111 rate.sleep();
00112 ros::spinOnce();
00113 if (status_)
00114 break;
00115 }
00116 }
00117 void waitUntilStart()
00118 {
00119 ros::Rate rate(50);
00120 while (ros::ok())
00121 {
00122 publishTransform();
00123 rate.sleep();
00124 ros::spinOnce();
00125 if (status_->status == trajectory_tracker_msgs::TrajectoryTrackerStatus::FOLLOWING)
00126 break;
00127 }
00128 }
00129 void publishPath(const std::vector<Eigen::Vector3d>& poses)
00130 {
00131 nav_msgs::Path path;
00132 path.header.frame_id = "odom";
00133 path.header.stamp = ros::Time::now();
00134
00135 for (const Eigen::Vector3d& p : poses)
00136 {
00137 const Eigen::Quaterniond q(Eigen::AngleAxisd(p[2], Eigen::Vector3d(0, 0, 1)));
00138
00139 geometry_msgs::PoseStamped pose;
00140 pose.header.frame_id = path.header.frame_id;
00141 pose.pose.position.x = p[0];
00142 pose.pose.position.y = p[1];
00143 pose.pose.orientation.x = q.x();
00144 pose.pose.orientation.y = q.y();
00145 pose.pose.orientation.z = q.z();
00146 pose.pose.orientation.w = q.w();
00147
00148 path.poses.push_back(pose);
00149 }
00150 pub_path_.publish(path);
00151 }
00152 void publishPathVelocity(const std::vector<Eigen::Vector4d>& poses)
00153 {
00154 trajectory_tracker_msgs::PathWithVelocity path;
00155 path.header.frame_id = "odom";
00156 path.header.stamp = ros::Time::now();
00157
00158 for (const Eigen::Vector4d& p : poses)
00159 {
00160 const Eigen::Quaterniond q(Eigen::AngleAxisd(p[2], Eigen::Vector3d(0, 0, 1)));
00161
00162 trajectory_tracker_msgs::PoseStampedWithVelocity pose;
00163 pose.header.frame_id = path.header.frame_id;
00164 pose.pose.position.x = p[0];
00165 pose.pose.position.y = p[1];
00166 pose.pose.orientation.x = q.x();
00167 pose.pose.orientation.y = q.y();
00168 pose.pose.orientation.z = q.z();
00169 pose.pose.orientation.w = q.w();
00170 pose.linear_velocity.x = p[3];
00171
00172 path.poses.push_back(pose);
00173 }
00174
00175 ros::Duration(0.5).sleep();
00176 pub_path_vel_.publish(path);
00177 }
00178 void publishTransform()
00179 {
00180 const Eigen::Quaterniond q(Eigen::AngleAxisd(yaw_, Eigen::Vector3d(0, 0, 1)));
00181 geometry_msgs::TransformStamped trans;
00182 trans.header.frame_id = "odom";
00183 trans.header.stamp = ros::Time::now() + ros::Duration(0.1);
00184 trans.child_frame_id = "base_link";
00185 trans.transform.translation.x = pos_[0];
00186 trans.transform.translation.y = pos_[1];
00187 trans.transform.rotation.x = q.x();
00188 trans.transform.rotation.y = q.y();
00189 trans.transform.rotation.z = q.z();
00190 trans.transform.rotation.w = q.w();
00191 tfb_.sendTransform(trans);
00192 }
00193 };
00194
00195 TEST_F(TrajectoryTrackerTest, StraightStop)
00196 {
00197 initState(Eigen::Vector2d(0, 0), 0);
00198
00199 std::vector<Eigen::Vector3d> poses;
00200 for (double x = 0.0; x < 0.5; x += 0.01)
00201 poses.push_back(Eigen::Vector3d(x, 0.0, 0.0));
00202 poses.push_back(Eigen::Vector3d(0.5, 0.0, 0.0));
00203 publishPath(poses);
00204
00205 waitUntilStart();
00206
00207 ros::Rate rate(50);
00208 const ros::Time start = ros::Time::now();
00209 while (ros::ok())
00210 {
00211 ASSERT_LT(ros::Time::now() - start, ros::Duration(10.0));
00212
00213 publishTransform();
00214 rate.sleep();
00215 ros::spinOnce();
00216 if (status_->status == trajectory_tracker_msgs::TrajectoryTrackerStatus::GOAL)
00217 break;
00218 }
00219 for (int i = 0; i < 25; ++i)
00220 {
00221 publishTransform();
00222 rate.sleep();
00223 ros::spinOnce();
00224 }
00225
00226 ASSERT_NEAR(yaw_, 0.0, 1e-2);
00227 ASSERT_NEAR(pos_[0], 0.5, 1e-2);
00228 ASSERT_NEAR(pos_[1], 0.0, 1e-2);
00229 }
00230
00231 TEST_F(TrajectoryTrackerTest, StraightStopConvergence)
00232 {
00233 const double vels[] =
00234 {
00235 0.02, 0.05, 0.1, 0.2, 0.5, 1.0
00236 };
00237 const double path_length = 2.0;
00238 for (const double vel : vels)
00239 {
00240 const std::string info_message = "linear vel: " + std::to_string(vel);
00241
00242 initState(Eigen::Vector2d(0, 0.01), 0);
00243
00244 std::vector<Eigen::Vector4d> poses;
00245 for (double x = 0.0; x < path_length; x += 0.01)
00246 poses.push_back(Eigen::Vector4d(x, 0.0, 0.0, vel));
00247 poses.push_back(Eigen::Vector4d(path_length, 0.0, 0.0, vel));
00248 publishPathVelocity(poses);
00249
00250 waitUntilStart();
00251
00252 ros::Rate rate(50);
00253 const ros::Time start = ros::Time::now();
00254 while (ros::ok())
00255 {
00256 ASSERT_LT(ros::Time::now() - start, ros::Duration(5.0 + path_length / vel)) << info_message;
00257
00258 publishTransform();
00259 rate.sleep();
00260 ros::spinOnce();
00261 if (status_->status == trajectory_tracker_msgs::TrajectoryTrackerStatus::GOAL)
00262 break;
00263 }
00264 for (int i = 0; i < 25; ++i)
00265 {
00266 publishTransform();
00267 rate.sleep();
00268 ros::spinOnce();
00269 }
00270
00271 EXPECT_NEAR(yaw_, 0.0, 1e-2) << info_message;
00272 EXPECT_NEAR(pos_[0], path_length, 1e-2) << info_message;
00273 EXPECT_NEAR(pos_[1], 0.0, 1e-2) << info_message;
00274 }
00275 }
00276
00277 TEST_F(TrajectoryTrackerTest, StraightVelocityChange)
00278 {
00279 initState(Eigen::Vector2d(0, 0), 0);
00280
00281 std::vector<Eigen::Vector4d> poses;
00282 for (double x = 0.0; x < 0.5; x += 0.01)
00283 poses.push_back(Eigen::Vector4d(x, 0.0, 0.0, 0.3));
00284 for (double x = 0.5; x < 1.5; x += 0.01)
00285 poses.push_back(Eigen::Vector4d(x, 0.0, 0.0, 0.5));
00286 poses.push_back(Eigen::Vector4d(1.5, 0.0, 0.0, 0.5));
00287 publishPathVelocity(poses);
00288
00289 waitUntilStart();
00290
00291 ros::Rate rate(50);
00292 const ros::Time start = ros::Time::now();
00293 while (ros::ok())
00294 {
00295 ASSERT_LT(ros::Time::now() - start, ros::Duration(10.0));
00296
00297 publishTransform();
00298 rate.sleep();
00299 ros::spinOnce();
00300 if (0.3 < pos_[0] && pos_[0] < 0.4)
00301 {
00302 ASSERT_NEAR(cmd_vel_->linear.x, 0.3, 1e-2);
00303 }
00304 else if (0.9 < pos_[0] && pos_[0] < 1.0)
00305 {
00306 ASSERT_NEAR(cmd_vel_->linear.x, 0.5, 1e-2);
00307 }
00308
00309 if (status_->status == trajectory_tracker_msgs::TrajectoryTrackerStatus::GOAL)
00310 break;
00311 }
00312 for (int i = 0; i < 25; ++i)
00313 {
00314 publishTransform();
00315 rate.sleep();
00316 ros::spinOnce();
00317 }
00318
00319 ASSERT_NEAR(yaw_, 0.0, 1e-2);
00320 ASSERT_NEAR(pos_[0], 1.5, 1e-2);
00321 ASSERT_NEAR(pos_[1], 0.0, 1e-2);
00322 }
00323
00324 TEST_F(TrajectoryTrackerTest, CurveFollow)
00325 {
00326 initState(Eigen::Vector2d(0, 0), 0);
00327
00328 std::vector<Eigen::Vector3d> poses;
00329 Eigen::Vector3d p(0.0, 0.0, 0.0);
00330 for (double t = 0.0; t < 1.0; t += 0.01)
00331 {
00332 p += Eigen::Vector3d(std::cos(p[2]) * 0.05, std::sin(p[2]) * 0.05, 0.005);
00333 poses.push_back(p);
00334 }
00335 for (double t = 0.0; t < 1.0; t += 0.01)
00336 {
00337 p += Eigen::Vector3d(std::cos(p[2]) * 0.05, std::sin(p[2]) * 0.05, 0.0);
00338 poses.push_back(p);
00339 }
00340 publishPath(poses);
00341
00342 waitUntilStart();
00343
00344 ros::Rate rate(50);
00345 const ros::Time start = ros::Time::now();
00346 while (ros::ok())
00347 {
00348 ASSERT_LT(ros::Time::now() - start, ros::Duration(20.0));
00349
00350 publishTransform();
00351 rate.sleep();
00352 ros::spinOnce();
00353 if (status_->status == trajectory_tracker_msgs::TrajectoryTrackerStatus::GOAL)
00354 break;
00355 }
00356 for (int i = 0; i < 25; ++i)
00357 {
00358 publishTransform();
00359 rate.sleep();
00360 ros::spinOnce();
00361 }
00362
00363 ASSERT_NEAR(yaw_, p[2], 1e-2);
00364 ASSERT_NEAR(pos_[0], p[0], 1e-1);
00365 ASSERT_NEAR(pos_[1], p[1], 1e-1);
00366 }
00367
00368 TEST_F(TrajectoryTrackerTest, InPlaceTurn)
00369 {
00370 const float init_yaw_array[] =
00371 {
00372 0.0,
00373 3.0
00374 };
00375 for (const float init_yaw : init_yaw_array)
00376 {
00377 initState(Eigen::Vector2d(0, 0), init_yaw);
00378
00379 const float target_angle_array[] =
00380 {
00381 0.5,
00382 -0.5
00383 };
00384 for (const float ang : target_angle_array)
00385 {
00386 std::vector<Eigen::Vector3d> poses;
00387 poses.push_back(Eigen::Vector3d(0.0, 0.0, init_yaw + ang));
00388 publishPath(poses);
00389
00390 waitUntilStart();
00391
00392 ros::Rate rate(50);
00393 const ros::Time start = ros::Time::now();
00394 while (ros::ok())
00395 {
00396 ASSERT_LT(ros::Time::now() - start, ros::Duration(10.0));
00397
00398 publishTransform();
00399 rate.sleep();
00400 ros::spinOnce();
00401
00402 if (cmd_vel_)
00403 {
00404 ASSERT_GT(cmd_vel_->angular.z * ang, -1e-2);
00405 }
00406 if (status_)
00407 {
00408 ASSERT_LT(status_->angle_remains * ang, 1e-2);
00409 }
00410
00411 if (status_->status == trajectory_tracker_msgs::TrajectoryTrackerStatus::GOAL)
00412 break;
00413 }
00414 ASSERT_TRUE(static_cast<bool>(cmd_vel_));
00415 for (int i = 0; i < 25; ++i)
00416 {
00417 publishTransform();
00418 rate.sleep();
00419 ros::spinOnce();
00420 }
00421
00422 ASSERT_NEAR(yaw_, init_yaw + ang, 1e-2);
00423 }
00424 }
00425 }
00426
00427 TEST_F(TrajectoryTrackerTest, SwitchBack)
00428 {
00429 initState(Eigen::Vector2d(0, 0), 0);
00430
00431 std::vector<Eigen::Vector3d> poses;
00432 Eigen::Vector3d p(0.0, 0.0, 0.0);
00433 for (double t = 0.0; t < 0.5; t += 0.01)
00434 {
00435 p -= Eigen::Vector3d(std::cos(p[2]) * 0.05, std::sin(p[2]) * 0.05, -0.01);
00436 poses.push_back(p);
00437 }
00438 for (double t = 0.0; t < 0.5; t += 0.01)
00439 {
00440 p += Eigen::Vector3d(std::cos(p[2]) * 0.05, std::sin(p[2]) * 0.05, 0.01);
00441 poses.push_back(p);
00442 }
00443 publishPath(poses);
00444
00445 waitUntilStart();
00446
00447 ros::Rate rate(50);
00448 const ros::Time start = ros::Time::now();
00449 while (ros::ok())
00450 {
00451 ASSERT_LT(ros::Time::now() - start, ros::Duration(10.0));
00452
00453 publishTransform();
00454 rate.sleep();
00455 ros::spinOnce();
00456 if (status_->status == trajectory_tracker_msgs::TrajectoryTrackerStatus::GOAL)
00457 break;
00458 }
00459 for (int i = 0; i < 25; ++i)
00460 {
00461 publishTransform();
00462 rate.sleep();
00463 ros::spinOnce();
00464 }
00465
00466 ASSERT_NEAR(yaw_, p[2], 1e-2);
00467 ASSERT_NEAR(pos_[0], p[0], 1e-1);
00468 ASSERT_NEAR(pos_[1], p[1], 1e-1);
00469 }
00470
00471 TEST_F(TrajectoryTrackerTest, SwitchBackWithPathUpdate)
00472 {
00473 initState(Eigen::Vector2d(0, 0), 0);
00474
00475 std::vector<Eigen::Vector3d> poses;
00476 std::vector<Eigen::Vector3d> poses_second_half;
00477 Eigen::Vector3d p(0.0, 0.0, 0.0);
00478 for (double t = 0.0; t < 0.5; t += 0.01)
00479 {
00480 p -= Eigen::Vector3d(std::cos(p[2]) * 0.05, std::sin(p[2]) * 0.05, -0.01);
00481 poses.push_back(p);
00482 }
00483 const Eigen::Vector2d pos_local_goal = p.head<2>();
00484 for (double t = 0.0; t < 1.0; t += 0.01)
00485 {
00486 p += Eigen::Vector3d(std::cos(p[2]) * 0.05, std::sin(p[2]) * 0.05, 0.01);
00487 poses.push_back(p);
00488 poses_second_half.push_back(p);
00489 }
00490 publishPath(poses);
00491
00492 waitUntilStart();
00493
00494 int cnt_arrive_local_goal(0);
00495 ros::Rate rate(50);
00496 const ros::Time start = ros::Time::now();
00497 while (ros::ok())
00498 {
00499 ASSERT_LT(ros::Time::now() - start, ros::Duration(10.0));
00500
00501 publishTransform();
00502 rate.sleep();
00503 ros::spinOnce();
00504 if (status_->status == trajectory_tracker_msgs::TrajectoryTrackerStatus::GOAL)
00505 break;
00506
00507 if ((pos_local_goal - pos_).norm() < 0.1)
00508 cnt_arrive_local_goal++;
00509
00510 if (cnt_arrive_local_goal > 25)
00511 publishPath(poses_second_half);
00512 else
00513 publishPath(poses);
00514 }
00515 for (int i = 0; i < 25; ++i)
00516 {
00517 publishTransform();
00518 rate.sleep();
00519 ros::spinOnce();
00520 }
00521
00522 ASSERT_NEAR(yaw_, p[2], 1e-2);
00523 ASSERT_NEAR(pos_[0], p[0], 1e-1);
00524 ASSERT_NEAR(pos_[1], p[1], 1e-1);
00525 }
00526
00527 void timeSource()
00528 {
00529 ros::NodeHandle nh("/");
00530 bool use_sim_time;
00531 nh.param("/use_sim_time", use_sim_time, false);
00532 if (!use_sim_time)
00533 return;
00534
00535 ros::Publisher pub = nh.advertise<rosgraph_msgs::Clock>("clock", 1);
00536
00537 ros::WallRate rate(500.0);
00538 ros::WallTime time = ros::WallTime::now();
00539 while (ros::ok())
00540 {
00541 rosgraph_msgs::Clock clock;
00542 clock.clock.fromNSec(time.toNSec());
00543 pub.publish(clock);
00544 rate.sleep();
00545 time += ros::WallDuration(0.01);
00546 }
00547 }
00548
00549 int main(int argc, char** argv)
00550 {
00551 testing::InitGoogleTest(&argc, argv);
00552 ros::init(argc, argv, "test_trajectory_tracker");
00553
00554 boost::thread time_thread(timeSource);
00555
00556 return RUN_ALL_TESTS();
00557 }